Building a Terminal File Manager with Rust
Introduction
This post explores the journey of building a terminal user interface (TUI) file manager in Rust. We'll examine Blaze Ultra, a practical CLI application that demonstrates key concepts in Rust development including terminal manipulation, event handling, and modern UI frameworks.
Why Rust for CLI Applications?
Rust has become increasingly popular for CLI tools due to several compelling advantages:
- Performance: Near C-level performance with zero-cost abstractions
- Safety: Memory safety without garbage collection prevents common bugs
- Ergonomics: Modern tooling with Cargo makes distribution trivial
- Cross-platform: Write once, compile anywhere with minimal platform-specific code
- Rich Ecosystem: Mature crates like
clap,ratatui, andtokioaccelerate development
Popular examples include ripgrep, fd, bat, and exa - all demonstrating Rust's capability to create fast, reliable command-line tools.
Project Overview: Blaze Ultra
Blaze Ultra is a TUI file manager that combines several powerful features:
- Interactive file browsing with keyboard navigation
- Fuzzy search powered by the
skimlibrary - Live preview of file contents
- Multi-panel layout for efficient file management
- Vim-style keybindings for power users
The project structure is intentionally minimal, with the core logic contained in a single main.rs file (~200 lines), making it an excellent learning resource.
Core Dependencies
Let's examine the key dependencies that power this application:
[dependencies]
clap = { version = "4.5", features = ["derive", "wrap_help", "color"] }
ratatui = "0.28"
crossterm = { version = "0.27", features = ["event-stream"] }
tokio = { version = "1", features = ["full"] }
skim = "0.10"
walkdir = "2"
bytesize = "1.3"
syntect = "5.2"
Key Libraries Explained
clap: The de-facto standard for command-line argument parsing. Using the derive macros makes defining CLI interfaces declarative and type-safe:
#[derive(Parser)]
#[command(name = "blaze", about = "TUI File Commander 2025", version)]
struct Args {
#[arg(default_value = ".")]
path: PathBuf,
}
ratatui: A modern TUI framework (fork of tui-rs) that provides widgets, layouts, and rendering primitives. It follows a retained-mode architecture where you describe what to render on each frame.
crossterm: Cross-platform terminal manipulation library handling raw mode, keyboard events, cursor control, and alternate screen buffers.
skim: A fuzzy finder library (similar to fzf) that enables fast, interactive searching through large datasets.
walkdir: Efficient directory traversal with configurable depth and filtering.
Application Architecture
State Management
The application state is encapsulated in a simple struct:
struct App {
current_path: PathBuf,
entries: Vec<walkdir::DirEntry>,
selected: usize,
active_panel: Panel,
preview: String,
search_query: String,
}
This immutable-first approach makes state transitions predictable. When navigating to a new directory, we create a fresh App instance rather than mutating deeply nested state.
Event Loop Pattern
The core of any TUI application is the event loop:
loop {
terminal.draw(|f| ui(f, &app))?;
if let crossterm::event::Event::Key(key) = read()? {
match key.code {
KeyCode::Char('q') => break,
KeyCode::Down => /* navigate down */,
KeyCode::Enter => /* open/preview */,
_ => {}
}
}
}
This pattern:
- Renders the current state
- Blocks waiting for user input
- Processes events and updates state
- Repeats
This synchronous approach works well for keyboard-driven applications. For more complex scenarios with async I/O, you'd integrate tokio more deeply.
Terminal Management
Proper terminal setup and cleanup is critical:
fn setup_terminal() -> Result<Terminal<CrosstermBackend<std::io::Stdout>>, Box<dyn std::error::Error>> {
enable_raw_mode()?;
let mut stdout = std::io::stdout();
execute!(stdout, EnterAlternateScreen, Hide)?;
Ok(Terminal::new(CrosstermBackend::new(stdout))?)
}
fn restore_terminals(terminal: &mut Terminal<CrosstermBackend<std::io::Stdout>>)
-> Result<(), Box<dyn std::error::Error>> {
disable_raw_mode()?;
execute!(terminal.backend_mut(), LeaveAlternateScreen, Show)?;
Ok(())
}
Raw mode disables line buffering and canonical processing, giving the application direct access to keypresses. The alternate screen preserves the user's terminal state, returning them to their original view on exit.
Building the UI with Ratatui
Ratatui uses a declarative layout system with constraints:
fn ui(f: &mut Frame, app: &App) {
let chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(40), Constraint::Percentage(60)])
.split(f.area());
// Left panel: file list
let list = List::new(items)
.block(Block::default().title("Files").borders(Borders::ALL))
.highlight_style(Style::default().bg(Color::Magenta));
// Right panel: preview
let preview = Paragraph::new(app.preview.as_str())
.block(Block::default().title("Preview").borders(Borders::ALL));
f.render_widget(list, chunks[0]);
f.render_widget(preview, chunks[1]);
}
The layout is recalculated on every frame based on terminal size, making the UI responsive without additional code.
Advanced Features
Fuzzy Search Integration
The fuzzy search temporarily exits the main TUI, launches skim's interface, then returns:
fn fuzzy_search(app: &mut App, terminal: &mut Terminal<...>) -> Result<...> {
restore_terminals(terminal)?; // Exit TUI mode
let items: Vec<String> = app.entries.iter()
.map(|e| e.path().display().to_string())
.collect();
let options = SkimOptionsBuilder::default()
.multi(false)
.build()?;
let (tx, rx) = unbounded();
for item in items {
tx.send(Arc::new(item))?;
}
let input = Skim::run_with(&options, Some(rx));
// Process selection...
*terminal = setup_terminal()?; // Re-enter TUI mode
Ok(())
}
This pattern of temporarily yielding terminal control to external tools is common in TUI applications.
File Preview with Safety
File reading includes error handling and size limits:
fn read_preview(path: &Path) -> String {
if path.is_dir() {
return "📁 DIRECTORY".to_string();
}
std::fs::read_to_string(path)
.unwrap_or_else(|_| "Cannot read file".into())
.lines()
.take(50) // Limit preview size
.collect::<Vec<_>>()
.join("\n")
}
This prevents loading massive files into memory and handles binary files gracefully.
Performance Optimizations
The release profile in Cargo.toml is aggressively optimized:
[profile.release]
lto = true # Link-time optimization
opt-level = 'z' # Optimize for size
strip = true # Remove debug symbols
panic = "abort" # Smaller panic handler
codegen-units = 1 # Better optimization (slower compile)
These settings produce a ~2MB binary that's blazingly fast while maintaining Rust's safety guarantees.
Key Takeaways
Building CLI applications in Rust teaches several important concepts:
- Terminal Abstractions: Understanding raw mode, alternate screens, and event handling
- UI Frameworks: Declarative layouts with constraint-based sizing
- State Management: Immutable patterns for predictable updates
- Error Handling:
Result<T, E>types force explicit error consideration - Zero-Cost Abstractions: High-level APIs that compile to efficient machine code
Next Steps
To extend this project, consider:
- Async file operations: Use
tokio::fsfor non-blocking I/O - Syntax highlighting: Integrate
syntectfor code preview - File operations: Add copy, move, delete with confirmation dialogs
- Bookmarks: Persistent favorite directories with
serde - Configuration: User-customizable keybindings and colors
- Testing: Unit tests for state transitions and integration tests for UI
Resources
- Blaze Ultra Repository
- Ratatui Documentation
- Crossterm Guide
- Command Line Apps in Rust
- Yazi File Manager - Advanced async TUI file manager
Conclusion
Rust's combination of performance, safety, and excellent library ecosystem makes it ideal for CLI development. Projects like Blaze Ultra demonstrate that you can build sophisticated terminal applications with relatively little code while maintaining the robustness Rust is known for.
The patterns explored here - event loops, terminal management, declarative UIs, and structured error handling - form the foundation for any Rust CLI application. Whether you're building file managers, system monitors, or development tools, these concepts will serve you well.
Happy coding! 🦀