A fast, keyboard-driven TUI file manager built in Rust, inspired by two-panel file managers like Midnight Commander.
- Single-panel file browser with vim-style navigation
- Copy and move files/directories — open a second panel to pick a destination
- Multi-item selection with shift-select
- Live filter (
/) to narrow directory listings - Pinned directories for quick access
- Navigate to any path instantly with
g(supports~expansion) - Create files and directories with full path support (
test/a/b.txt) - Delete files and directories with confirmation
- Open items in
$EDITORor with the default application - Sort by name, date modified, extension, or size
- Zip selected items; extract
.zipand.tar.gzarchives - Nerd Font icons in the file list
- Catppuccin Mocha colour theme
- Persists pinned directories across sessions
- Exits to the active directory via a shell wrapper
lfm uses Nerd Font icons in the file list. Your terminal must use a Nerd Font patched typeface, otherwise icons render as placeholder boxes.
Install a Nerd Font:
-
macOS (Homebrew):
brew install --cask font-jetbrains-mono-nerd-font
Then set your terminal font to JetBrainsMono Nerd Font (or whichever you installed).
-
Linux:
Download a font from nerdfonts.com/font-downloads, unzip into~/.local/share/fonts/, then runfc-cache -fv. -
Windows:
Download and install from nerdfonts.com/font-downloads, then select the font in your terminal emulator settings.
cargo build --release
# copy target/release/lfm somewhere on your $PATHAdd to ~/.zshrc (or ~/.bashrc):
lfm() {
local tmp
tmp=$(mktemp)
LFM_CHOOSEDIR="$tmp" command lfm "$@"
local dir
dir=$(cat "$tmp")
rm -f "$tmp"
[[ -n "$dir" && -d "$dir" ]] && cd "$dir"
}| Key | Action |
|---|---|
j / ↓ |
Move down |
k / ↑ |
Move up |
h / ← |
Go to parent directory |
l / → |
Enter directory |
Tab |
Next panel |
Shift+Tab |
Previous panel |
| Key | Action |
|---|---|
J / Shift+↓ |
Mark item and move down |
K / Shift+↑ |
Mark item and move up |
Esc |
Clear selection |
| Key | Action |
|---|---|
r |
Rename current item |
g |
Go to path (supports ~) |
n |
Create file or directory (end path with / for directory) |
d |
Delete selected or current item (with confirmation) |
c |
Copy selected or current item — opens destination panel, C with rename before |
m |
Move selected or current item — opens destination panel, M with rename before |
e |
Open selected item in $EDITOR |
o |
Open with default application |
s |
Cycle sort order: name → date → ext → size |
z |
Zip selected or current item(s) |
u |
Extract .zip or .tar.gz archive |
| Key | Action |
|---|---|
/ |
Enter filter mode |
↑ / ↓ |
Move selection while filtering |
Enter / Esc |
Exit filter, restore path and selection |
| Key | Action |
|---|---|
p |
Open pinned panel |
p (in panel) |
Pin current or selected directory |
Enter / Space |
Navigate to pinned directory |
d (in panel) |
Delete pinned directory |
Esc |
Close pinned panel |
| Key | Action |
|---|---|
c |
Start copy — right panel opens at current directory |
h/l/j/k |
Navigate destination panel |
Enter |
Confirm copy into selected directory (or current dir) |
Esc |
Cancel copy |
| Key | Action |
|---|---|
m |
Start move — right panel opens at current directory |
h/l/j/k |
Navigate destination panel |
Enter |
Confirm move into selected directory (or current dir) |
Esc |
Cancel move |
| Key | Action |
|---|---|
? |
Show keybinding help |
q |
Quit and cd to active directory |
On quit, lfm saves the pinned directory list to ~/.config/lfm/state.json.
cargo build # compile
cargo run # compile and run
cargo test # run tests
cargo fmt # format code
cargo clippy -- -D warnings -W clippy::pedantic # lint (hard mode)Built with ratatui.
lfm follows an Elm-style MVU (Model-View-Update) pattern. All state lives in an immutable Model; user input produces Message values; update is a pure function that returns a new Model plus an optional Effect; side effects (I/O, spawning threads) are executed in main.
keyboard event
│
▼
to_message() ← input mode intercept (Filter / NewPath / Copy / …)
│ Message
▼
update() ← pure; returns (Model, Effect)
│
┌───┴──────────────────────────────────┐
│ Model │ Effect
▼ ▼
view() spawn thread / open editor /
(ratatui render) write state / quit
Background file transfers run in a dedicated OS thread and send ProgressMsg values over an mpsc channel. The main loop drains this channel each iteration and fires Message::ProgressTick / Message::ProgressDone into update so the progress bar stays live without blocking input.