| 275 | } |
| 276 | |
| 277 | fn read_directory( |
| 278 | path: &Path, |
| 279 | offset: usize, |
| 280 | limit: usize, |
| 281 | title: String, |
| 282 | ) -> Result<ToolResult, ToolError> { |
| 283 | let mut entries: Vec<String> = Vec::new(); |
| 284 | |
| 285 | for entry in WalkDir::new(path) |
| 286 | .max_depth(1) |
| 287 | .follow_links(true) |
| 288 | .into_iter() |
| 289 | .filter_map(|e| e.ok()) |
| 290 | { |
| 291 | if entry.path() == path { |
| 292 | continue; |
| 293 | } |
| 294 | |
| 295 | let name = entry.file_name().to_string_lossy().to_string(); |
| 296 | if entry.file_type().is_dir() { |
| 297 | entries.push(format!("{}/", name)); |
| 298 | } else { |
| 299 | entries.push(name); |
| 300 | } |
| 301 | } |
| 302 | |
| 303 | entries.sort(); |
| 304 | |
| 305 | let start = offset.saturating_sub(1); |
| 306 | let sliced: Vec<&str> = entries |
| 307 | .iter() |
| 308 | .skip(start) |
| 309 | .take(limit) |
| 310 | .map(|s| s.as_str()) |
| 311 | .collect(); |
| 312 | let truncated = start + sliced.len() < entries.len(); |
| 313 | |
| 314 | let output = format!( |
| 315 | "<path>{}</path>\n<type>directory</type>\n<entries>\n{}\n{}{}\n</entries>", |
| 316 | path.display(), |
| 317 | sliced.join("\n"), |
| 318 | if truncated { |
| 319 | format!( |
| 320 | "\n(Showing {} of {} entries. Use 'offset' parameter to read beyond entry {})", |
| 321 | sliced.len(), |
| 322 | entries.len(), |
| 323 | offset + sliced.len() |
| 324 | ) |
| 325 | } else { |
| 326 | format!("\n({} entries)", entries.len()) |
| 327 | }, |
| 328 | "" |
| 329 | ); |
| 330 | |
| 331 | let preview = sliced |
| 332 | .iter() |
| 333 | .take(20) |
| 334 | .cloned() |