fast-fs

Crates.iofast-fs
lib.rsfast-fs
version0.2.0
created_at2026-01-10 21:23:49.792938+00
updated_at2026-01-10 21:23:49.792938+00
descriptionHigh-speed async file system traversal library with batteries-included file browser component
homepage
repositoryhttps://github.com/5ocworkshop/fast-libraries
max_upload_size
id2034695
size326,043
(5ocworkshop)

documentation

https://docs.rs/fast-fs

README

fast-fs

Crates.io Documentation License: 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

[dependencies]
fast-fs = "0.2"
tokio = { version = "1.48", features = ["rt", "fs"] }

Quick Start

Directory Reading

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)

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

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

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:

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);

FileEntry

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

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:

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

Variant Description
Name / NameDesc Alphabetical
Size / SizeDesc By file size
Modified / ModifiedDesc By modification time
Extension By file extension
DirsFirst Directories before files

GitignoreMatcher

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:

use std::sync::{Arc, Mutex};
let browser = Arc::new(Mutex::new(Browser::new(config)?));

Documentation

Testing

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

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 in Cargo.toml.

Commit count: 0

cargo fmt