Search for files and directories matching a query with high performance.
(
self,
query: str,
*,
path: str | None = None,
include_directories: bool = True,
include_files: bool = True,
depth: int = 3,
limit: int = 100,
)
| 308 | return self.get_details(path, contents=contents).file |
| 309 | |
| 310 | def search( |
| 311 | self, |
| 312 | query: str, |
| 313 | *, |
| 314 | path: str | None = None, |
| 315 | include_directories: bool = True, |
| 316 | include_files: bool = True, |
| 317 | depth: int = 3, |
| 318 | limit: int = 100, |
| 319 | ) -> list[FileInfo]: |
| 320 | """Search for files and directories matching a query with high performance.""" |
| 321 | if not query.strip(): |
| 322 | return [] |
| 323 | |
| 324 | search_path = path if path is not None else self.get_root() |
| 325 | if not os.path.exists(search_path): |
| 326 | return [] |
| 327 | |
| 328 | query_lower = query.lower() |
| 329 | |
| 330 | # Compile regex pattern for case-insensitive search |
| 331 | try: |
| 332 | pattern = re.compile(re.escape(query_lower)) |
| 333 | except re.error: |
| 334 | # If regex compilation fails, fall back to simple string matching |
| 335 | pattern = None |
| 336 | |
| 337 | results: list[FileInfo] = [] |
| 338 | seen_paths: set[str] = set() |
| 339 | |
| 340 | # Use BFS with deque for better performance than recursive DFS |
| 341 | queue = deque([(search_path, 0)]) # (path, current_depth) |
| 342 | |
| 343 | while queue and len(results) < limit: |
| 344 | current_path, current_depth = queue.popleft() |
| 345 | |
| 346 | # Skip if we've exceeded depth limit |
| 347 | if current_depth > depth: |
| 348 | continue |
| 349 | |
| 350 | # Skip if we've already processed this path (avoid symlink loops) |
| 351 | if current_path in seen_paths: |
| 352 | continue |
| 353 | seen_paths.add(current_path) |
| 354 | |
| 355 | try: |
| 356 | # Use os.scandir for better performance than os.listdir |
| 357 | with os.scandir(current_path) as entries: |
| 358 | for entry in entries: |
| 359 | if len(results) >= limit: |
| 360 | break |
| 361 | |
| 362 | # Skip ignored files/directories |
| 363 | if entry.name in SEARCH_IGNORE_LIST: |
| 364 | continue |
| 365 | |
| 366 | # Check if name matches query |
| 367 | name_lower = entry.name.lower() |