| 1 | //! Shared workspace discovery filters for UI path pickers and mentions. |
| 2 | |
| 3 | use std::path::Path; |
| 4 | |
| 5 | /// Directories that must remain discoverable for `@`-mention completion and |
| 6 | /// fuzzy file resolution even when excluded by `.gitignore`. |
| 7 | pub(crate) const DISCOVERY_ALWAYS_DIRS: &[&str] = &[".deepseek", ".cursor", ".claude", ".agents"]; |
| 8 | |
| 9 | /// Root-relative directories that are too large or generated to discover |
| 10 | /// with gitignore disabled. Exact user-specified paths may still resolve. |
| 11 | const DISCOVERY_EXCLUDED_SUBDIRS: &[&str] = |
| 12 | &[".deepseek/snapshots", ".worktrees", ".claude/worktrees"]; |
| 13 | |
| 14 | /// Directory basenames that should not be traversed by fallback discovery |
| 15 | /// walks that deliberately disable gitignore. |
| 16 | const DISCOVERY_EXCLUDED_DIR_NAMES: &[&str] = &[ |
| 17 | ".git", |
| 18 | "target", |
| 19 | "node_modules", |
| 20 | ".venv", |
| 21 | "venv", |
| 22 | "env", |
| 23 | "dist", |
| 24 | "build", |
| 25 | ".next", |
| 26 | ".turbo", |
| 27 | "coverage", |
| 28 | "__pycache__", |
| 29 | ".pytest_cache", |
| 30 | ".ruff_cache", |
| 31 | ]; |
| 32 | |
| 33 | /// Check whether `path` is under a root-relative excluded discovery subtree. |
| 34 | pub(crate) fn path_is_excluded_from_discovery(walk_root: &Path, path: &Path) -> bool { |
| 35 | DISCOVERY_EXCLUDED_SUBDIRS |
| 36 | .iter() |
| 37 | .any(|excluded| path.starts_with(walk_root.join(excluded))) |
| 38 | } |
| 39 | |
| 40 | /// Filter for walks that turn off gitignore to surface explicit hidden paths. |
| 41 | pub(crate) fn should_skip_unignored_discovery_entry(walk_root: &Path, path: &Path) -> bool { |
| 42 | if path == walk_root { |
| 43 | return false; |
| 44 | } |
| 45 | |
| 46 | if path_is_excluded_from_discovery(walk_root, path) { |
| 47 | return true; |
| 48 | } |
| 49 | |
| 50 | path.file_name() |
| 51 | .and_then(|name| name.to_str()) |
| 52 | .is_some_and(|name| DISCOVERY_EXCLUDED_DIR_NAMES.contains(&name)) |
| 53 | } |
| 54 |