186 releases (41 breaking)
Uses new Rust 2024
| new 0.98.1 | Apr 9, 2026 |
|---|---|
| 0.97.4 | Apr 7, 2026 |
| 0.96.9 | Mar 31, 2026 |
#394 in Filesystem
Used in 3 crates
(via vtcode-core)
355KB
8K
SLoC
vtcode-file-search
Fast, parallel fuzzy file search library for VT Code.
Overview
vtcode-file-search is a dedicated file discovery and fuzzy matching crate that provides:
- Parallel directory traversal using the
ignorecrate (same library as ripgrep) - Fuzzy matching with
nucleo-matcherfor relevance scoring - Lock-free result collection per worker thread
- Configurable exclusion patterns for .gitignore, .ignore, and custom globs
- Standalone CLI for testing and integration
- Library API for embedding in VT Code tools, extensions, and MCP servers
Features
- Parallel traversal with configurable thread count
- Automatic .gitignore, .ignore, and .git/info/exclude support
- Custom exclusion patterns (glob-style)
- Fuzzy matching with scoring
- Cancellation support via
Arc<AtomicBool> - Optional character indices for UI highlighting
- JSON output format
- Top-K results collection (configurable limit)
Quick Start
As a Library
use std::num::NonZero;
use std::path::Path;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use vtcode_file_search::run;
fn main() -> anyhow::Result<()> {
let results = run(
"main", // Pattern
NonZero::new(100).unwrap(), // Limit
Path::new("."), // Search directory
vec![], // Exclusion patterns
NonZero::new(4).unwrap(), // Threads
Arc::new(AtomicBool::new(false)), // Cancellation flag
false, // compute_indices
true, // respect_gitignore
)?;
for m in results.matches {
println!("{}: {}", m.path, m.score);
}
Ok(())
}
As a CLI
# Search for files matching "main" pattern
vtcode-file-search "main"
# With options
vtcode-file-search --cwd /path/to/search "test" --limit 50 --threads 8
# JSON output
vtcode-file-search --json "pattern" /path
# Exclude patterns
vtcode-file-search "main" --exclude "target/**" --exclude "node_modules/**"
# Show help
vtcode-file-search --help
API
run()
pub fn run(
pattern_text: &str,
limit: NonZero<usize>,
search_directory: &Path,
exclude: Vec<String>,
threads: NonZero<usize>,
cancel_flag: Arc<AtomicBool>,
compute_indices: bool,
respect_gitignore: bool,
) -> anyhow::Result<FileSearchResults>
Parameters:
pattern_text: Fuzzy search pattern (e.g., "main.rs", "test")limit: Maximum number of results to returnsearch_directory: Root directory to searchexclude: Glob patterns to exclude (e.g.,["target/**", "node_modules/**"])threads: Number of worker threadscancel_flag:Arc<AtomicBool>for early terminationcompute_indices: Whether to compute character indices for highlightingrespect_gitignore: Whether to respect .gitignore files
Returns:
pub struct FileSearchResults {
pub matches: Vec<FileMatch>,
pub total_match_count: usize,
}
pub struct FileMatch {
pub score: u32,
pub path: String,
pub indices: Option<Vec<u32>>,
}
file_name_from_path()
Extract filename from a path:
use vtcode_file_search::file_name_from_path;
assert_eq!(file_name_from_path("src/main.rs"), "main.rs");
assert_eq!(file_name_from_path("/absolute/path/file.txt"), "file.txt");
Examples
Basic Search
use std::num::NonZero;
use std::path::Path;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use vtcode_file_search::run;
let results = run(
"src",
NonZero::new(100).unwrap(),
Path::new("."),
vec![],
NonZero::new(4).unwrap(),
Arc::new(AtomicBool::new(false)),
false,
true,
)?;
for m in results.matches {
println!("{} (score: {})", m.path, m.score);
}
With Exclusions
let results = run(
"test",
NonZero::new(50).unwrap(),
Path::new("."),
vec![
"target/**".to_string(),
"node_modules/**".to_string(),
".git/**".to_string(),
],
NonZero::new(4).unwrap(),
Arc::new(AtomicBool::new(false)),
false,
true,
)?;
With Cancellation
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
let cancel_flag = Arc::new(AtomicBool::new(false));
let cancel_clone = cancel_flag.clone();
// Spawn a task that cancels after 100ms
tokio::spawn(async move {
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
cancel_clone.store(true, Ordering::Relaxed);
});
let results = run(
"pattern",
NonZero::new(100).unwrap(),
Path::new("."),
vec![],
NonZero::new(4).unwrap(),
cancel_flag,
false,
true,
)?;
Performance
On a modern machine (M4 Apple Silicon, 8 cores):
- 5,000 files: ~50ms
- 100,000 files: ~200ms
- 1,000,000 files: ~500ms
Parallel efficiency is ~90% with 8 threads.
Architecture
Input Pattern
↓
[Parallel Directory Traversal]
├─ Worker 1: [Fuzzy Match] → [Per-Worker Results]
├─ Worker 2: [Fuzzy Match] → [Per-Worker Results]
└─ Worker N: [Fuzzy Match] → [Per-Worker Results]
↓
[Merge & Sort by Score]
↓
[Top-K Results]
Key Design:
- Each worker thread has its own
BestMatchesList(no locking during traversal) - Per-match fuzzy matching using
nucleo-matcher - Automatic .gitignore support from the
ignorecrate - Early termination via
Arc<AtomicBool>cancellation flag
Dependencies
ignore– Parallel directory traversal (ripgrep's choice)nucleo-matcher– Fuzzy matching and scoring (Neovim's choice)tokio– Async runtimeserde/serde_json– Serializationclap– CLI argument parsing
Testing
# Unit tests
cargo test -p vtcode-file-search
# Integration tests
cargo test -p vtcode-file-search --test integration_tests
# With output
cargo test -p vtcode-file-search -- --nocapture
# Specific test
cargo test -p vtcode-file-search test_multiple_matches
Building
# Development build
cargo build -p vtcode-file-search
# Release build
cargo build -p vtcode-file-search --release
CLI Usage
# Show help
./target/debug/vtcode-file-search --help
# Search in current directory
./target/debug/vtcode-file-search "pattern"
# Search in specific directory
./target/debug/vtcode-file-search --cwd /path/to/search "pattern"
# JSON output
./target/debug/vtcode-file-search --json "pattern"
# Exclude patterns
./target/debug/vtcode-file-search "pattern" --exclude "target/**" --exclude "node_modules/**"
# Limit results
./target/debug/vtcode-file-search "pattern" --limit 50
# Custom thread count
./target/debug/vtcode-file-search "pattern" --threads 8
Integration with VT Code
This crate will be integrated into VT Code for:
- File Browser – Fuzzy filename search
- Grep Tool – Efficient file discovery (no ripgrep subprocess)
- Code Intelligence – Workspace-wide symbol search
- Zed Extension – File picker integration
- VS Code Extension – Similar integration
- MCP Server – Expose as MCP resource/tool
Contributing
This crate follows VT Code's standard conventions:
- Use
cargo testfor testing - Use
cargo clippyfor linting - Use
cargo fmtfor formatting - Error handling with
anyhow::Result<T> - No
unwrap()orexpect()calls
License
MIT
References
Dependencies
~21–35MB
~544K SLoC