| 124 | } |
| 125 | |
| 126 | async fn grep(&self, request: WorkspaceGrepRequest) -> Result<WorkspaceGrepResult> { |
| 127 | let pattern = if request.case_insensitive { |
| 128 | format!("(?i){}", request.pattern) |
| 129 | } else { |
| 130 | request.pattern.clone() |
| 131 | }; |
| 132 | let regex = regex::Regex::new(&pattern)?; |
| 133 | let glob = request |
| 134 | .glob |
| 135 | .as_deref() |
| 136 | .map(glob::Pattern::new) |
| 137 | .transpose() |
| 138 | .map_err(|e| anyhow!("invalid glob: {}", e))?; |
| 139 | let base_prefix = if request.base.is_root() { |
| 140 | String::new() |
| 141 | } else { |
| 142 | format!("{}/", request.base.as_str()) |
| 143 | }; |
| 144 | let files = self.files.read().unwrap(); |
| 145 | let mut output = String::new(); |
| 146 | let mut match_count = 0; |
| 147 | let mut matched_files = 0; |
| 148 | |
| 149 | for (file_path, content) in files.iter() { |
| 150 | if !file_path.starts_with(&base_prefix) { |
| 151 | continue; |
| 152 | } |
| 153 | let remaining = &file_path[base_prefix.len()..]; |
| 154 | if glob.as_ref().is_some_and(|glob| !glob.matches(remaining)) { |
| 155 | continue; |
| 156 | } |
| 157 | |
| 158 | let mut file_matched = false; |
| 159 | for (idx, line) in content.lines().enumerate() { |
| 160 | if regex.is_match(line) { |
| 161 | file_matched = true; |
| 162 | match_count += 1; |
| 163 | output.push_str(&format!(">{}:{}: {}\n", file_path, idx + 1, line)); |
| 164 | } |
| 165 | } |
| 166 | if file_matched { |
| 167 | matched_files += 1; |
| 168 | } |
| 169 | } |
| 170 | |
| 171 | Ok(WorkspaceGrepResult { |
| 172 | output, |
| 173 | match_count, |
| 174 | file_count: matched_files, |
| 175 | truncated: false, |
| 176 | }) |
| 177 | } |
| 178 | } |
| 179 | |
| 180 | #[derive(Default)] |