# fast-fs
[](https://crates.io/crates/fast-fs)
[](https://docs.rs/fast-fs)
[](https://opensource.org/licenses/MIT)
High-speed async file system traversal and navigation library for Rust.
## Overview
fast-fs provides two main capabilities:
1. **Directory Reading Functions** - Simple async functions for traversing directories
2. **Navigation Module (`nav`)** - A batteries-included file browser state machine for building file pickers, save dialogs, and project explorers
The library is framework-agnostic: you handle rendering and input capture, fast-fs manages all browser state, navigation logic, and file operations.
## Features
- **Async Directory Reading** - Non-blocking traversal using tokio
- **Streaming API** - Memory-efficient traversal with backpressure support
- **Gitignore Support** - Built-in .gitignore and .ignore pattern matching
- **Advanced Filtering** - Depth limits, extensions, glob patterns, hidden files
- **File Browser Component** - Complete state machine with vim-style keybindings
- **Multi-Selection** - Range selection with Shift+Arrow, toggle with Space
- **Clipboard Model** - Cut/copy intent tracking with conflict detection
- **Navigation History** - Back/forward with H/L keys
- **Parent Directory Entry** - Optional ".." entry at top of file list
- **File Categorization** - Classify files as Code, Image, Video, Document, etc.
- **UI Integration Helpers** - `scroll_offset()`, `visible_range()` for viewport management
## Installation
```toml
[dependencies]
fast-fs = "0.2"
tokio = { version = "1.48", features = ["rt", "fs"] }
```
## Quick Start
### Directory Reading
```rust
use fast_fs::{read_dir, read_dir_recursive, TraversalOptions};
#[tokio::main]
async fn main() -> Result<(), fast_fs::Error> {
// Read a single directory
let files = read_dir("/path/to/dir").await?;
// Recursive with options
let options = TraversalOptions::default()
.with_max_depth(3)
.with_extensions(&["rs", "toml"]);
let all_files = read_dir_recursive("/project", options).await?;
for file in all_files {
println!("{}: {} bytes", file.name, file.size);
}
Ok(())
}
```
### File Browser (nav module)
```rust
use fast_fs::nav::{Browser, BrowserConfig, KeyInput, ActionResult};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = BrowserConfig::open_dialog();
let mut browser = Browser::new(config).await?;
loop {
// Render UI using browser.files(), browser.cursor(), etc.
let key = get_user_input(); // Your framework
match browser.handle_key(key).await {
ActionResult::FileSelected(path) => {
println!("Selected: {}", path.display());
break;
}
ActionResult::NeedsConfirmation(op) => {
let confirmed = ask_user(&op.message());
browser.resolve_confirmation(confirmed).await?;
}
ActionResult::NeedsInput(req) => {
let input = prompt_user(req.prompt());
browser.complete_input(&input).await?;
}
_ => {}
}
}
Ok(())
}
```
## API Reference
### Directory Reading Functions
```rust
use fast_fs::{read_dir, read_dir_visible, read_dir_recursive, read_dir_stream};
// Async single directory
let files = read_dir("/path").await?;
// Non-hidden files only
let visible = read_dir_visible("/path").await?;
// Recursive with options
let options = TraversalOptions::default().with_max_depth(5);
let all = read_dir_recursive("/path", options).await?;
// Streaming (memory-efficient)
let stream = read_dir_stream("/path", options);
```
### TraversalOptions
```rust
use fast_fs::TraversalOptions;
let options = TraversalOptions::default()
.with_gitignore(true) // Respect .gitignore (default: true)
.with_max_depth(10) // Limit recursion depth
.with_extensions(&["rs", "py"]) // Filter by extension
.with_patterns(&["*.tmp"]) // Custom ignore patterns
.include_hidden(); // Include hidden files
```
### Navigation Module
The `nav` module provides a complete file browser component:
| `Browser` | Core state machine (owns all browser state) |
| `BrowserConfig` | Configuration with presets |
| `KeyInput` | Framework-agnostic key events |
| `KeyMap` | Configurable key bindings |
| `Action` | User actions (move, select, delete, etc.) |
| `ActionResult` | Action outcomes |
| `Selection` | Multi-selection with range support |
| `ClipboardState` | Cut/copy intent tracking |
| `History` | Back/forward navigation |
| `FileCategory` | File type categorization |
**Configuration Presets:**
```rust
use fast_fs::nav::BrowserConfig;
// Read-only file picker (shows ".." parent entry)
let config = BrowserConfig::open_dialog();
// Writable, no confirmations
let config = BrowserConfig::save_dialog();
// Full-featured explorer
let config = BrowserConfig::project_explorer();
// Customize: disable parent entry
let config = BrowserConfig::open_dialog()
.with_show_parent_entry(false);
```
**Default Key Bindings (vim-style):**
| `j/k` or arrows | Move up/down |
| `Shift+Up/Down` | Range selection (extend) |
| `Enter/l` | Open/select |
| `h/Backspace` | Parent directory |
| `H/L` | History back/forward |
| `Space` | Toggle selection |
| `Ctrl+A` | Select all |
| `Ctrl+X` | Cut |
| `Ctrl+C` | Copy |
| `d` | Delete |
| `r` | Rename |
| `/` | Filter |
| `.` | Toggle hidden |
| `s` | Cycle sort (includes extension) |
| `PageUp/PageDown` | Page navigation (caller handles) |
| Any letter | Typeahead jump (caller handles) |
**Typeahead & Page Navigation:**
```rust
// Page navigation (caller provides viewport height)
browser.page_up(terminal_height);
browser.page_down(terminal_height);
// Windows-style typeahead: press 'a' repeatedly to cycle through 'a' files
browser.jump_to_char('a');
// Substring search: jump to files containing "doc"
browser.jump_to_substring("doc");
```
**UI Integration Helpers:**
The browser provides state accessors for building your render loop:
```rust
// Get viewport boundaries for rendering
let start = browser.scroll_offset();
let range = browser.visible_range(terminal_height);
// Render only visible files
for i in range {
let entry = &browser.files()[i];
let is_cursor = i == browser.cursor();
let is_selected = browser.selection().contains(i);
render_file_row(entry, is_cursor, is_selected);
}
// Check if parent ".." entry is shown
if browser.has_parent_entry() {
// First entry is the parent directory
}
```
**Clipboard Model:**
The library tracks cut/copy intent; the caller handles actual clipboard and paste operations:
```rust
use fast_fs::nav::{ActionResult, ClipboardState};
// Store clipboard state when user cuts/copies
let mut app_clipboard: Option<ClipboardState> = None;
match browser.handle_key(key).await {
ActionResult::Clipboard(state) => {
println!("{}", state.message()); // "copy 3 items"
app_clipboard = Some(state);
}
// ...
}
// Later, check for conflicts before pasting
if let Some(ref clipboard) = app_clipboard {
let conflicts = clipboard.would_conflict(browser.current_path());
// Caller performs actual file copy/move
}
```
**Custom Key Bindings:**
```rust
use fast_fs::nav::{KeyMap, KeyInput, Action, BrowserConfig};
// Start with defaults and customize
let mut keymap = KeyMap::default();
keymap.unbind(KeyInput::Ctrl('c')); // Free for terminal interrupt
keymap.bind(KeyInput::Char('y'), Action::Copy); // Use 'y' instead
let config = BrowserConfig::default().with_keymap(keymap);
```
### FileEntry
```rust
pub struct FileEntry {
pub path: PathBuf,
pub name: String,
pub is_dir: bool,
pub is_hidden: bool,
pub size: u64,
pub modified: Option<SystemTime>,
pub is_symlink: bool,
pub is_readonly: bool,
}
// Lazy methods (syscalls on demand)
entry.is_executable() // Check execute permission
entry.resolve_symlink() // Get symlink target
entry.is_symlink_broken() // Check if target exists
entry.category() // Get FileCategory
```
### FileCategory
```rust
use fast_fs::nav::FileCategory;
// Categorize by extension
let cat = FileCategory::from_extension("rs"); // Code
let cat = FileCategory::from_extension("png"); // Image
// From FileEntry (checks symlink, dir, executable first)
let cat = entry.category();
```
Categories: `Directory`, `Text`, `Code`, `Image`, `Video`, `Audio`, `Archive`, `Document`, `Executable`, `Symlink`, `Unknown`
### FileList
Versioned file list with deferred sorting:
```rust
let mut list = FileList::new();
list.push_batch(files);
list.set_sort(SortBy::Size); // Deferred
list.set_show_hidden(true); // Deferred
list.catchup(); // Sort happens here
```
### SortBy
| `Name` / `NameDesc` | Alphabetical |
| `Size` / `SizeDesc` | By file size |
| `Modified` / `ModifiedDesc` | By modification time |
| `Extension` | By file extension |
| `DirsFirst` | Directories before files |
### GitignoreMatcher
```rust
use fast_fs::GitignoreMatcher;
let matcher = GitignoreMatcher::from_path("/project")?;
if matcher.is_ignored(&path, is_dir) {
// Path matches .gitignore patterns
}
// Add patterns at runtime
matcher.add_pattern("*.log")?;
```
## Thread Safety
`Browser` is `Send` but not `Sync`. For shared access:
```rust
use std::sync::{Arc, Mutex};
let browser = Arc::new(Mutex::new(Browser::new(config)?));
```
## Documentation
- [API Documentation](https://docs.rs/fast-fs) - Complete API reference on docs.rs
- [Quick Start Guide](https://github.com/5ocworkshop/fast-libraries/blob/main/crates/fast-fs/docs/QUICKSTART.md) - Get started in 5 minutes
- [Navigation API Reference](https://github.com/5ocworkshop/fast-libraries/blob/main/crates/fast-fs/docs/API.md) - Detailed nav module reference
## Testing
```bash
cargo test -p fast-fs
```
199 tests total: 76 unit tests + 113 integration tests + 10 doc tests covering directory reading, filtering, navigation, selection, clipboard, and file operations.
## License
MIT
## Author
Made with care in [Madeira, Portugal](https://visitmadeira.com/en/)
GitHub: [5ocworkshop](https://github.com/5ocworkshop)
---
> **Note on source file versions:** This crate uses [OFPF (One Function Per File)](https://github.com/5ocworkshop/ofpf) methodology. Version numbers in source file headers (e.g., `VERSION: 0.5.0`) track individual file edit history, not the crate release version. The crate release version is defined in `Cargo.toml`.