| Crates.io | fast-fs |
| lib.rs | fast-fs |
| version | 0.2.0 |
| created_at | 2026-01-10 21:23:49.792938+00 |
| updated_at | 2026-01-10 21:23:49.792938+00 |
| description | High-speed async file system traversal library with batteries-included file browser component |
| homepage | |
| repository | https://github.com/5ocworkshop/fast-libraries |
| max_upload_size | |
| id | 2034695 |
| size | 326,043 |
High-speed async file system traversal and navigation library for Rust.
fast-fs provides two main capabilities:
nav) - A batteries-included file browser state machine for building file pickers, save dialogs, and project explorersThe library is framework-agnostic: you handle rendering and input capture, fast-fs manages all browser state, navigation logic, and file operations.
scroll_offset(), visible_range() for viewport management[dependencies]
fast-fs = "0.2"
tokio = { version = "1.48", features = ["rt", "fs"] }
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(())
}
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(())
}
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);
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
The nav module provides a complete file browser component:
| Type | Description |
|---|---|
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:
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):
| Key | Action |
|---|---|
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:
// 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:
// 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:
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:
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);
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
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
Versioned file list with deferred sorting:
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
| Variant | Description |
|---|---|
Name / NameDesc |
Alphabetical |
Size / SizeDesc |
By file size |
Modified / ModifiedDesc |
By modification time |
Extension |
By file extension |
DirsFirst |
Directories before files |
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")?;
Browser is Send but not Sync. For shared access:
use std::sync::{Arc, Mutex};
let browser = Arc::new(Mutex::new(Browser::new(config)?));
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.
MIT
Made with care in Madeira, Portugal
GitHub: 5ocworkshop
Note on source file versions: This crate uses OFPF (One Function Per File) 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 inCargo.toml.