(&self, path: &WorkspacePath)
| 331 | } |
| 332 | |
| 333 | async fn list_dir(&self, path: &WorkspacePath) -> WorkspaceResult<Vec<WorkspaceDirEntry>> { |
| 334 | // Synthesize a directory view from the flat key space. For a |
| 335 | // requested directory at `path`, anything stored under |
| 336 | // `<path>/<rest>` shows up. Mid-path components become Directory |
| 337 | // entries; direct children become File entries. |
| 338 | let prefix: String = if path.is_root() { |
| 339 | String::new() |
| 340 | } else { |
| 341 | format!("{}/", path.as_str()) |
| 342 | }; |
| 343 | let state = self.state.lock().unwrap(); |
| 344 | let mut seen_dirs: std::collections::HashSet<String> = std::collections::HashSet::new(); |
| 345 | let mut entries: Vec<WorkspaceDirEntry> = Vec::new(); |
| 346 | let mut any = false; |
| 347 | for (key, (content, _)) in state.files.iter() { |
| 348 | let Some(rest) = key.strip_prefix(&prefix) else { |
| 349 | continue; |
| 350 | }; |
| 351 | if rest.is_empty() { |
| 352 | continue; |
| 353 | } |
| 354 | any = true; |
| 355 | if let Some((dir_name, _)) = rest.split_once('/') { |
| 356 | if seen_dirs.insert(dir_name.to_string()) { |
| 357 | entries.push(WorkspaceDirEntry { |
| 358 | name: dir_name.to_string(), |
| 359 | kind: super::WorkspaceFileType::Directory, |
| 360 | size: 0, |
| 361 | }); |
| 362 | } |
| 363 | } else { |
| 364 | entries.push(WorkspaceDirEntry { |
| 365 | name: rest.to_string(), |
| 366 | kind: super::WorkspaceFileType::File, |
| 367 | size: content.len() as u64, |
| 368 | }); |
| 369 | } |
| 370 | } |
| 371 | if !path.is_root() && !any { |
| 372 | return Err(WorkspaceError::NotFound { |
| 373 | path: path.as_str().to_string(), |
| 374 | }); |
| 375 | } |
| 376 | entries.sort_by(|a, b| a.name.cmp(&b.name)); |
| 377 | Ok(entries) |
| 378 | } |
| 379 | } |
| 380 | |
| 381 | #[async_trait] |
no test coverage detected