Rodeo is a terminal file manager inspired by Norton and Midnight Commander, written in Rust. It pairs the classic dual-pane layout with Vim-style keybindings, a rich preview, and themes — with no runtime dependencies beyond a terminal.
-
Dual-pane navigation with per-pane sorting, filtering and hidden-file toggling; the active pane is highlighted and the inactive one dimmed.
-
Tree view (
t) — a collapsible tree in place of a pane's flat listing, rooted where the pane already is, for getting around a deep project without walking into it one directory at a time. Nodes open lazily, file operations work from a tree row as from a flat one, and a copy or move recreates the directories the sources were listed under. -
File operations — copy, move, rename, mkdir, touch, delete to trash (with a permanent-delete fallback), symlink creation (
L, pointing at the source from the other pane), and a permissions/ownership popup (C: chmod as octal digits or an rwx toggle grid, plus chown by name or numeric id) — on the selection or the highlighted entry. Large transfers run in the background with a progress gauge and can be cancelled. -
Preview (
SpaceorF3) — syntax highlighting via syntect, images, archive listings (zip/tar/tar.gz), PDF text, directory sizes, and hex dumps with metadata for binaries. Slow content loads on a worker thread behind a spinner; long lines wrap (wtoggles). -
Search — one query language everywhere: type a plain word and it is matched fuzzily, type a regular expression and it is matched as one. No mode to pick first.
/opens the file finder: every file and directory below the active pane, searched by name as you type.Entertakes the pane to the match (into a directory, or onto the file with its parent listed),Ctrl+eopens it ineditor.Ctrl+ffilters the pane listing in place, with matches highlighted.Ctrl+gis find-in-files: greps file contents below the active pane — type a regex,Enterruns it,Enteron a hit opens that file ineditorat the matching line.
Both popups are split Telescope-style — results on the left, a syntax highlighted preview on the right (
Ctrl+n/Ctrl+pmove the selection,Ctrl+d/Ctrl+uscroll the preview) — and both obey the search filter below, which they name along their bottom border. -
Git aware — entry names are coloured by status, the header shows the branch and counts, and wide terminals get a status column showing the raw porcelain code (so staged and unstaged changes are distinguishable).
-
Bookmarks —
bbookmarks the entry under the cursor (or the pane's own directory, when the cursor is on..),Blists them.Enteror1–9jumps,dremoves, and entries whose target has since disappeared are flagged(missing)so they can be pruned withP. One that merely cannot be read right now — an unreadable parent, a stalled network mount — is flagged(unreadable)instead and is never pruned. -
Power tools — bulk rename with regex or numbering (
R), a trash browser (:trash), on-demand directory sizes (S), wildcard selection (*), and a command palette (:) that can capture command output or hand over the terminal. -
Live refresh — both panes follow filesystem changes automatically.
-
Themes — ten bundled palettes; syntax colours are derived from the active theme.
brew install lordgreg/rodeo/rodeocurl -fsSL https://raw.githubusercontent.com/lordgreg/rodeo/master/install.sh | bashRequires a Rust toolchain (edition 2024, so Rust 1.85+; developed on 1.95).
git clone https://github.com/lordgreg/rodeo
cd rodeo
cargo install --path .Themes are looked up in, first match wins:
$XDG_DATA_HOME/rodeo/themes(usually~/.local/share/rodeo/themes) — your own themes$XDG_DATA_DIRS/rodeo/themes, e.g./usr/share/rodeo/themes<binary's dir>/../share/rodeo/themes— thebin/sharelayout a package manager installs into (Homebrew ships the bundled themes here)<binary's dir>/themes— running straight out of an extracted release archive, without installing it./themes, for running from a checkout
cargo install --path . only installs the binary, so its themes are not on
that search path automatically; copy them where rodeo can find them:
mkdir -p ~/.local/share/rodeo/themes
cp themes/*.toml ~/.local/share/rodeo/themes/Rodeo starts even with no themes installed — it falls back to a compiled-in
copy of the default theme. Homebrew and the release archives carry the full
set already; the copy above is only needed for a cargo install from
source.
A man page is checked in at docs/rodeo.1. Installed automatically by
Homebrew; for a from-source install:
sudo install -Dm644 docs/rodeo.1 /usr/local/share/man/man1/rodeo.1brew uninstall lordgreg/rodeo/rodeo # Homebrew
rm -f ~/.local/bin/rodeo ~/.local/share/man/man1/rodeo.1
rm -rf ~/.local/share/rodeo # install.sh (default PREFIX)
# if installed with PREFIX=/usr/local instead:
sudo rm -f /usr/local/bin/rodeo /usr/local/share/man/man1/rodeo.1
sudo rm -rf /usr/local/share/rodeo
cargo uninstall rodeo # cargo install --path .None of these touch ~/.config/rodeo (config, bookmarks) or
~/.local/share/rodeo/themes (your own themes) — remove those too for a
clean slate.
rodeo [--left <PATH>] [--right <PATH>] [--theme <NAME>] [--config <FILE>]
rodeo --help lists the flags and where configuration and themes are read
from.
~/.config/rodeo/config.toml, created with defaults on first run. --config <FILE> uses that file instead, for the whole session: :w writes it, :so
re-reads it, and it is created with defaults if it is not there yet.
theme = "catppuccin-macchiato" # name in the themes directory, or a path to a .toml
initial_directory_left = "/home/you"
initial_directory_right = "/home/you"
sort_type = "Name" # Name | Size | Time | Flagged
sort_order = "Ascending" # Ascending | Descending
show_hidden = false
directories_on_top = true
active_pane = "Left" # Left | Right
editor = "nvim" # defaults to $VISUAL, then $EDITOR, then vi
icons = false # file-type glyphs; needs a Nerd Font
# What `/` (find files) and `Ctrl+g` (find in files) are allowed to look at.
filter_gitignore = true # skip whatever .gitignore/.ignore exclude
filter_hidden = true # skip dot-files and dot-directories
filter_entries = ["target", "node_modules", "*.lock", "src/generated"]
# extra names: a name, *.ext, or a sub-path
# Optional. The key is on the left, what it does on the right: either an
# action name, a `:command`, or "none" to free the key.
[keybindings]
"Q" = "quit" # add a key for a built-in action
"ctrl+r" = "refresh" # modifiers work: ctrl+, alt+, shift+
"z" = ":term lazygit" # run a command, exactly as if typed after `:`
"f9" = ":!git status --short"
"q" = "none" # free a keyOverriding a key rodeo already uses is allowed, but it says so on startup —
and warns loudly if an action is left with no key at all. :so reloads the
bindings without restarting.
Action names: open parent first last select
select_all glob sizes quit left right switch help
preview search filter find palette rename create yank paste
paste_move delete_chord copy move delete down up hidden
refresh sort_next sort_prev sort_reverse bulk_rename bookmark
bookmarks permissions symlink tree tree_expand tree_collapse
:so reloads the config at runtime, :w writes the current settings back.
Bookmarks live in bookmarks.toml beside config.toml (so --config ./rodeo.toml keeps its own set next to it), as a plain list of paths:
paths = ["/home/you/src/rodeo", "/etc/nginx/nginx.conf"]They are written the moment one changes, not on :w — config.toml is yours
to edit, and folding machine-managed state into it would rewrite your settings
on every keypress. A missing or malformed bookmarks.toml starts an empty
list with a warning rather than refusing to start.
Bundled themes: default, catppuccin-frappe, catppuccin-latte,
catppuccin-macchiato, catppuccin-mocha, dracula, github-dark, nord,
solarized-dark, tokyo-night. Switch at runtime with :theme <name>.
The defaults are vim-first: no function keys, one key per job. ? shows this
list in the app, and the bar along the bottom always shows the keys that are
actually bound — rebind something and the bar says so.
| Key | Action |
|---|---|
j k / ↓ ↑ |
Move cursor |
g / G |
First / last entry |
h / l / Tab |
Left pane / right pane / switch |
Enter |
Open directory, or edit file in $EDITOR |
Backspace |
Parent directory (tree: re-root one level up) |
t |
Tree view on/off |
→ / ← |
Tree: open/close a directory, or step in/out |
Space |
Preview (w wraps, Ctrl+f/b page, Ctrl+d/u half-page, Ctrl+j/k scroll) |
x / * / Ctrl+a |
Toggle selection / select by wildcard / select all |
y / p / P |
Yank / paste copy / paste move |
Y / M |
Copy / move to the other pane (one-key y+Tab+p) |
L |
Symlink the selection into the other pane |
r |
Rename |
R |
Bulk rename (2+ selected) |
C |
Permissions/ownership (chmod/chown) |
b / B |
Bookmark the entry (or the pane's directory on ..) / list bookmarks |
a |
Create file, or directory with a / suffix |
dd, Del |
Move to trash |
/ |
Find files by name (fuzzy or regex) |
Ctrl+f / Ctrl+g |
Filter the pane / find in files |
S |
Compute directory sizes |
Shift+←/→ / Shift+O |
Change sort column / reverse order |
Ctrl+h / Ctrl+l |
Toggle hidden files / refresh |
: |
Command palette |
? |
Help (and the version, on the bottom border) |
Esc |
Close popup, clear filter, then clear selection |
q |
Quit |
Function keys are not bound by default: they duplicate keys that already
exist, and terminals steal several of them (F10 opens the menu in GNOME
Terminal, F1 opens help in others). If you want them anyway, paste this into
config.toml — :so applies it without a restart, and the footer relabels
itself to match:
[keybindings]
f1 = "help"
f2 = "rename"
f3 = "preview"
f4 = "open"
f5 = "copy"
f6 = "move"
f7 = "create" # a directory needs the `/` suffix; `:mkdir <name>` is the direct route
f8 = "delete"
f10 = "quit": opens the command line. Matching commands are listed as you type, with
their arguments and a description; Tab walks the list and Shift+Tab goes
back, Vim-wildmenu style. Arguments complete too — directories for :e/:cd,
theme names for :theme.
| Command | Action |
|---|---|
:q :quit |
Quit |
:w :write |
Save the configuration |
:so :source |
Reload the configuration |
:e :cd <path> |
Navigate to a directory |
:mkdir <name> |
Create a directory |
:touch <name> |
Create an empty file |
:rename <new> |
Rename the current entry |
:delete |
Trash the selected or current entries |
:theme [name] |
Switch theme, or list the available ones |
:trash |
Browse the trash |
:bookmarks |
Browse the bookmarks |
:term <cmd> |
Run a command attached to the terminal (lazygit, htop…) |
:!<cmd> |
Run a shell command and show its output |
:help |
Show the help popup |
%f expands to the selected (or highlighted) paths in both :! and :term —
:!wc -l %f, :term nvim %f.
The two differ in who owns the terminal:
:!<cmd>captures stdout and stderr and shows them in the scrollable preview popup. Use it for output you want to read::!git log --oneline.:term <cmd>hands the terminal to the program — rodeo leaves the alternate screen, runs it attached, and comes back when it exits. Use it for anything interactive::term lazygit,:term htop,:term $SHELL.
Interactive programs open /dev/tty directly rather than writing to stdout, so
running one under :! produces no capturable output: it will work, but rodeo
cannot show you anything afterwards and says so in the footer. :term is the
one to reach for.
Pushing a v* tag builds and publishes archives for Linux x86_64 and macOS
(Apple silicon):
git tag -a v0.1.0 -m "rodeo 0.1.0"
git push origin v0.1.0.github/workflows/release.yml does the work. Each archive carries the binary,
the themes, the man page, the README and the licence, plus a .sha256. The
same workflow can be started from the Actions tab against a tag that already
exists, to retry a failed publish or to re-cut assets without moving the tag —
with optional draft and dry-run.
renovate.json drives a self-hosted Renovate
that runs daily from .github/workflows/renovate.yml, or on demand from the
Actions tab (with optional dry-run and debug logging). It needs a
RENOVATE_TOKEN secret — see the header of that workflow.
Patch and minor crate updates are grouped into one pull request, major ones
arrive separately, and ratatui and its ecosystem move together because they do
not compile apart. cargo deny check in CI is the other half of this: Renovate
proposes newer versions, cargo-deny fails the build on advisories.
cargo test # unit + integration tests
cargo clippy --all-targets -- -D warnings
cargo fmt --check
cargo deny check # licences and security advisories
cargo run --example gen_man # regenerate docs/rodeo.1 after CLI changesOne-time per clone, enable the repo's git hooks:
git config core.hooksPath scripts/git-hookspre-commit— whenCargo.toml/src/cli.rsare staged, checksdocs/rodeo.1hasn't drifted from the CLI definition; when any.rsorCargo.toml/Cargo.lockis staged, runs clippy.pre-push— when pushing avX.Y.Ztag, checks it matches theCargo.tomlversion and runs the full test suite.
The crate is a library (src/lib.rs) with a thin binary, so integration tests
can reach the internals.
Licensed under the Apache License, Version 2.0 — see LICENSE.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in rodeo shall be licensed as above, without any additional terms or conditions.
