The fastest GUI file manager on Linux.
Keyboard first, native to Omarchy, and it holds only the rows you can see.
Nautilus gets four things wrong on Hyprland: the keyboard is an afterthought, remote and cloud handling is painful, it is slow and heavy, and it does not look like the rest of the desktop. Flea is a standalone application built for Omarchy from the ground up, not a Nautilus extension. A Quickshell front end over a Rust backend that keeps the whole directory and hands the window only what fits on screen.
Flea is built for Omarchy. Preferred commands: omarchy pkg add flea, then omarchy update; AUR
path only for flea-git.
omarchy pkg add fleaTo make Flea the default file manager:
flea --defaultThis sets Flea as the inode/directory handler, makes Omarchy's two file-manager keys,
SUPER + SHIFT + F and SUPER + ALT + SHIFT + F, open it instead of Nautilus, and claims the file
chooser described below. Run flea --default off before omarchy pkg drop flea to restore the
previous handlers and remove it.
To make Flea the file chooser every application opens, the dialog behind omarchy tailscale send
and every Flatpak's Open and Save:
flea --picker
systemctl --user restart xdg-desktop-portalFlea implements org.freedesktop.impl.portal.FileChooser, so this replaces the GTK dialog for every
portal caller on the box at once. It writes one interface key to
~/.config/xdg-desktop-portal/portals.conf and no default, so screen sharing, screenshots and every
other portal keep the backend they already had. It also adds one Hyprland rule that gives the chooser
the same floating treatment Omarchy already gives the GTK one. flea --picker off puts both back.
To track main instead of releases, use the AUR package:
omarchy pkg aur add flea-gitFour optional packages each unlock one feature and nothing else: libarchive for archive listing
and extraction, 7zip for .7z archives, imagemagick for image conversion, and tailscale for
Taildrop sharing.
docs/install.md has the rest: what lands on disk, what flea --default writes
and how to undo it by hand, and how the package proves itself.
omarchy updateFlea updates with Omarchy. Your flea --default choice survives because it is a preference, not a
package file.
Two fixtures on one box, caches dropped before every launch, three runs per entrant, medians of the three. Every entrant is started by its own launcher, the way you would start it, on an idle box: the harness waits rather than start a run above a one-minute load average of 0.50.
Every table and every place below is printed by
tools/flea-bench-report from two runs kept on disk and read by column
name: scale-rc-2026.csv for the 100,000 file fixture and media-rc-2044.csv for the media
fixture, each beside the manifest that records the box, the fixture and the versions. The
method, the fixtures and what each column actually measures are in
docs/benchmarks.md.
Entrant versions, read off the installed artefact by the harness rather than typed in. GUI: flea
at target/release/flea as built 2026-09-02 20:13:50, 972,008 bytes; nautilus 50.2.2-1, thunar
4.20.9-1, pcmanfm 1.4.0-2, nemo 6.6.4-1, dolphin 26.08.0-4, and strata v0.6.1 built from
source. TUI, every one of them under kitty 0.48.2-1: yazi 26.8.15-1, mc 4.8.33-1, broot
1.59.0-1, nnn 5.3-1, lf 42-1, ranger 1.9.4-5, xplr 1.0.1-1, superfile 1.6.0-1.
Both runs measured that binary as they found it. The harness never rebuilds, and it refuses to
start when src, Cargo.toml or Cargo.lock is newer than the binary, which none of them was;
it deliberately does not check ui/, because QML is read at run time and never compiled in. The
tree both runs measured is b857757, clean, and the last ui/ change in it is b5d9735.
Anything committed after that is not in the tables below.
Every timing below is a magnitude from one machine and not a citable constant. What survives a
re-run is the ordering and the size of the gaps, not the digits. Two batches of this harness on
this box, hours apart on the same day and with nothing aimed at either, put Flea's settle lead over
dolphin on the scale fixture at 4.27x and then at 4.57x, and moved lf's settle time on the
media fixture from 631 ms to 1,744 ms, which is 2.8x.
Every timing carries the work beside it. 2,612 ms against 14,514 ms is 36 thumbnails against 552, one screenful against fifteen, and the time column on its own would say the opposite of what happened. The GUI and TUI brackets are judged apart and never share a table, because a TUI previews the one file under the cursor where a GUI renders a grid of them.
Work is equal by construction. The thumbnailable denominator is 0: every entrant lists the same 100,000 entries and the fixture holds nothing any of them could thumbnail, so this run took no thumbnail count at all and the work column says so rather than printing a zero nobody measured. The times compare straight across.
| entrant | first window | settled listing | work done | memory, PSS | CPU, process tree | runs |
|---|---|---|---|---|---|---|
flea |
752 ms | 1,166 ms | not measured | 106.7 MiB | 0.96 s | 3 |
dolphin |
681 ms | 5,339 ms | not measured | 221.3 MiB | 9.02 s | 3 |
strata |
790 ms | 7,856 ms | not measured | 113.0 MiB | 7.62 s | 3 |
nemo |
737 ms | 22,315 ms | not measured | 460.9 MiB | 25.53 s | 3 |
thunar |
511 ms | 23,084 ms | not measured | 348.0 MiB | 22.70 s | 3 |
pcmanfm |
410 ms | 35,785 ms | not measured | 111.3 MiB | 24.80 s | 3 |
nautilus |
793 ms | 79,025 ms | not measured | 334.3 MiB | 18.80 s | 3 |
Column by column, and the one column Flea does not win is in the same list as the three it does:
- Time to a settled listing: 1,166 ms, first of seven, ahead of
dolphinat 5,339 ms, 4.57x. Flea's three runs settled at 1,398, 1,152 and 1,166 ms, and the earlier batch of the same day read 4.27x on this comparison, so take the lead as about 4.5x rather than as a digit. - Memory, PSS: 106.7 MiB, first of seven, ahead of
pcmanfmat 111.3 MiB, 1.04x. Flea runs a second process, its backend, sampled separately at 5.3 MiB; the pair reads 112.0 MiB, which is pastpcmanfm. That rise is Flea's own and not measurement noise, and it is not yet attributed to a commit. The column above samples Flea the way it samples every other entrant. - CPU, process tree: 0.96 s, first of seven, ahead of
strataat 7.62 s, 7.93x. - Time to first window: 752 ms, fifth of seven, behind
pcmanfmat 410 ms, 1.83x.
Fifth to paint a window and first to be usable, and those are not the same column. pcmanfm puts a
frame on screen in 410 ms and then takes about 35.8 seconds to finish the listing Flea finishes in
about 1.2 seconds. A window that is drawn but still filling is not a file manager you can use yet,
which is why the settle column is the one this project optimises and the first-window column is
reported rather than chased.
The CPU column charges every entrant its whole process tree, so an out-of-process thumbnailer is counted against it. The memory column does not: it sampled the window process, plus Flea's backend because Flea is the entrant that has one, which understates any rival whose work happens elsewhere.
| entrant | first window | settled listing | work done | memory, PSS | CPU, process tree | runs |
|---|---|---|---|---|---|---|
flea |
833 ms | 2,612 ms | 36 thumbnails | 112.6 MiB | 1.42 s | 3 |
nemo |
765 ms | 3,372 ms | 60 thumbnails | 55.3 MiB | 5.13 s | 3 |
thunar |
511 ms | 12,785 ms | 221 thumbnails | 40.7 MiB | 7.27 s | 3 |
dolphin |
672 ms | 14,514 ms | 552 thumbnails | 101.5 MiB | 68.11 s | 3 |
nautilus |
819 ms | 17,006 ms | 541 thumbnails | 172.6 MiB | 12.81 s | 3 |
strata |
798 ms | 30,792 ms | 205 thumbnails | 70.3 MiB | 1.82 s | 3 |
Not ranked. An entrant whose work was not measured cannot be compared on time, and one that never settled has no time.
| entrant | settled listing | work done | why it is not ranked |
|---|---|---|---|
pcmanfm |
never settled | 605 thumbnails | never settled in any of the 3 runs |
strata's three rows were taken a day after the rest of the field. It persists no thumbnail, so the
cache count every other row uses saw nothing and it sat unranked while it was in fact drawing 205 of
them; the re-run counts that work by a live watch across the same three runs that take the timing.
The method, the run conditions and the two differences from the batch are in
docs/bench/media-rc-2044.manifest.md.
Flea settles first here while drawing the fewest thumbnails of any ranked entrant: 36 against
dolphin's 552. That first place means nothing read apart from the work column beside it, because
the two were not asked the same question. Flea's 36 is the viewport and nothing else, by design:
thumbnails are asked for only when the list stops moving and only for the rows on screen, which is
the same design that takes the settle column on the 100,000 file fixture.
Column by column. The denominator moves because an entrant that never settled still has a window, a
memory and a CPU number, so pcmanfm is counted on three of these four:
- Time to a settled listing: 2,612 ms, first of six, ahead of
nemoat 3,372 ms, 1.29x, and at a fifteenth ofdolphin's work, as above. - CPU, process tree: 1.42 s, first of seven, ahead of
strataat 1.82 s, 1.28x. - Memory, PSS: 112.6 MiB, sixth of seven, behind
pcmanfmat 40.5 MiB, 2.78x; fifth of the six ranked entrants, behindthunarat 40.7 MiB. With Flea's backend, 114.6 MiB, and still sixth. - Time to first window: 833 ms, seventh of seven, behind
pcmanfmat 390 ms, 2.13x; sixth of the six ranked entrants, behindthunarat 511 ms.
Ranking strata cost Flea a place on memory and cut the CPU lead from 3.61x over nemo to 1.28x
over strata, now the nearest rival on that column: 1.82 s against Flea's 1.42 s, where the next
entrant is nemo at 5.13 s. It is lighter than Flea too, 70.3 MiB against 112.6, while drawing 205
thumbnails to Flea's 36. What it does not take is the settle column, where it is the slowest ranked
entrant here at 11.8x Flea's time.
First window moved the wrong way between the earlier batch of the same day and this one: 689 ms to 752 ms on the scale fixture, and 774 ms to 833 ms here, where Flea is seventh of seven. It has never been a column Flea won and it blocks nothing. The scale move is smaller than the range across Flea's own three runs in that batch, 732 to 913 ms; the media move is larger than its own range of 795 to 835 ms, so read the media one as a move and the scale one as noise.
Speed is not the only column. The field run above cannot answer this one: the fixture's names sort
by format, so an entrant that settles early never reaches the photos and its per-format counts say
where it stopped rather than what it can do. A separate probe,
tools/flea-bench-capability, gives every entrant one file per
format, a private cache and forty-five seconds, ranks nothing and times nothing, and counts what
landed by md5 key against the fixture's own map.
| entrant | jpg | png | webp | heic | mp4 | webm | mkv | txt |
|---|---|---|---|---|---|---|---|---|
flea |
yes | yes | yes | yes | yes | yes | yes | - |
nautilus |
yes | yes | yes | yes | yes | yes | - | - |
thunar |
yes | yes | yes | yes | yes | yes | yes | - |
pcmanfm |
- | yes | - | - | yes | yes | - | - |
nemo |
yes | - | yes | - | - | - | - | - |
dolphin |
yes | yes | yes | - | yes | yes | yes | - |
strata |
yes | yes | yes | - | yes | yes | yes | - |
txt is the control: no thumbnailer draws a text file, so an entrant claiming one there would be a broken measurement rather than a capable entrant.
strata's row is the one measured by a live watch rather than by a thumbnail-cache count, because
it is the one entrant that persists no thumbnail: it renders each one into a scratch directory it
deletes immediately and keeps the image in memory. A cache count sees none of that, which is why
this table read "thumbnails nothing" for a whole release. The field run above now counts its work
the same way, in its own runs.
The field run's own per-format counts answer the other question, how far each entrant got before it
stopped, and they must not be read as the table above. Flea's zero in jpg here is the viewport it
drew, not a format it cannot produce.
| entrant | thumbnails | by format, run 1 |
|---|---|---|
flea |
36 | heic=0;jpg=0;mkv=6;mp4=24;png=0;txt=0;webm=6;webp=0;unknown=0 |
nautilus |
541 | heic=13;jpg=0;mkv=0;mp4=400;png=14;txt=0;webm=100;webp=14;unknown=0 |
thunar |
221 | heic=0;jpg=0;mkv=37;mp4=148;png=0;txt=0;webm=36;webp=0;unknown=0 |
pcmanfm |
605 | heic=0;jpg=0;mkv=0;mp4=400;png=105;txt=0;webm=100;webp=0;unknown=0 |
nemo |
60 | heic=0;jpg=45;mkv=0;mp4=0;png=0;txt=0;webm=0;webp=15;unknown=0 |
dolphin |
552 | heic=0;jpg=50;mkv=84;mp4=335;png=0;txt=0;webm=83;webp=0;unknown=0 |
strata |
205 | heic=0;jpg=0;mkv=34;mp4=137;png=0;txt=0;webm=34;webp=0;unknown=0 |
Judged apart and on a different measure. Every entrant runs under kitty in the configuration its own project documents, recorded verbatim in the manifest. These are the media fixture's rows, the run with a file worth previewing in it. The first-window column here is the terminal's own startup cost and is not comparable to a GUI row. An entrant with no image preview reads N/A, never 0. Flea's own terminal interface is not built yet, so it does not appear.
| entrant | first window | settled listing | time to first preview | preview runs |
|---|---|---|---|---|
yazi |
274 ms | 2,033 ms | 753 ms | 3 of 3 |
mc |
275 ms | 860 ms | N/A | 0 of 3 |
broot |
275 ms | 460 ms | 676 ms | 1 of 3 |
nnn |
275 ms | 501 ms | 737 ms | 3 of 3 |
lf |
257 ms | 1,744 ms | 735 ms | 3 of 3 |
ranger |
274 ms | 1,006 ms | 1,123 ms | 3 of 3 |
xplr |
293 ms | 605 ms | N/A | 0 of 3 |
superfile |
293 ms | 1,162 ms | N/A | 0 of 3 |
A preview cell of N/A means no run recorded one: mc, xplr and superfile recorded -1 in all
three runs, which is the harness saying it never saw a preview rather than that it saw a fast one,
and broot's 676 ms is one run of three. That sentinel is dropped and never averaged in, and it is
the same rule that keeps pcmanfm's never-settled media row below the rule above instead of
sorting it to first place. This is the least stable column in either bracket: between the two
batches of the same day superfile went from a preview in all three runs to none, and broot went
from none to one of three.
A grid of thumbnails, for when the names are not the point.
A Miller columns board, with the preview and the file's facts in the last column.
And Space opens a Quick Look over any of them. PDFs page, media plays, archives list.
The status bar is the running commentary. It carries the item count and the filesystem's free space at its ends, and the last operation and its undo in between.
Ctrl-Shift-n makes a folder and opens the rename field on it with the stem already selected, so the name is one typed word away.
- Finder's natural name ordering, directories first.
file_2comes beforefile_10, case is ignored, and leading zeros are worth nothing. Directories group ahead of files, in both front ends, and the backend'slistsorts them that way so no view can disagree. - A preview column for text, images, video and audio, PDF with page navigation, and an archive's contents. Video and audio play in place.
- Thumbnails from the shared freedesktop cache the whole desktop reads and writes, for jpg, png, webp, heic, mp4, webm and mkv. Measured against the field, only thunar matches that set; dolphin has no heic and nautilus no mkv. They are asked for only when the list stops moving and only for the rows on screen, so a fling through a directory asks for nothing and nothing is generated for a row you did not look at.
- Thumbnailing is mandatory-sandboxed. Without
bwrapandprlimitonPATHthe job is refused rather than run unconfined. - File operations with an undo journal. Copy, cut, paste, trash, rename, duplicate,
compress, extract and convert, each reversible with
z. - Network and cloud in the rail. SMB, SFTP, FTPS, WebDAV and NFS mounts through
gio, Taildrop to a peer, and Dropbox as a first-class destination. Local disks and removable volumes group below them under DEVICES, which the screenshots here crop away rather than retouch: that row is labelled with the machine's own hostname. - A path bar and directory tabs.
:orCtrl+Ltypes a path, with Tab completion over the directories one level down;t,wand1to9open, close and switch up to nine tabs in one window, and only one listing is ever live. - Columns you choose. Right click the column titles to hide Mode, Size, Date Modified or
Kind; the choice outlives the window, and the pane's width still wins over a column it cannot
carry.
Ctrl+Shift+PlusandMinuswalk Flea's text size along Omarchy's own stops,Ctrl+Shift+0goes back to following the desktop, and the settings panel's Display section is the same one setting. - A settings panel on
,and on the toolbar's sliders button, with three working groups: a text size that follows Omarchy or pins one of its stops; per-action context-menu visibility with one tri-state master over the six basic actions; and a Mac/Windows keyboard preset over that one key table. Nothing else is in it yet. - It looks like Omarchy because it reads the live palette, the same tokens the shell bar uses, and every mark is drawn in the Omarchy cut, which is its own section below.
, from anywhere in the window, or the sliders button at the right end of the toolbar. Three
sections, and only three, because a rail row onto a page with no working control is worse than no
row:
- Display. Text size follows Omarchy by default, and that is the whole of it until you switch
the row to Override, which pins one of Omarchy's own seven stops: 9, 10, 11, 12, 14, 16 or 20 px.
Ctrl+Shift+PlusandCtrl+Shift+Minuswalk the same stops andCtrl+Shift+0goes back to following, so the chord and the row are one setting and not two. Body text, captions, padding, row height, icon slots and marks all derive from the size in force. The monitor scale and the corner rounding are read-only beside it: Flea shows the compositor's values and never steps one. - Menus. Every action the context menu can build, switched on or off one at a time, with a tri-state master over Cut, Copy, Paste, Duplicate, Rename and Move to Trash that reads the enabled count, "5 of 6". Open and Show hidden files are listed but locked: a menu that cannot open the row under the cursor is not a menu. A row that leaves takes its separator with it, and the change lands on the menu's next open. Hiding a row never touches its key. Show keyboard hints is the one row here that is not an action: off, which is how it ships, no menu prints the key beside a row and an empty folder offers no tip; on, both appear. Every chord is bound either way.
- Keys. Mac or Windows, over the one
keys.tomltable. Everything the two platforms agree on is shared and answers under both; the preset carries only the chords where they differ, and it rebinds in the window at once. Press?for the whole map.
The choices live in ~/.local/state/flea/ui.json, the one file Flea keeps for itself, beside the
column set and everything else that outlives a window. The text size is stored as {"mode":"system"}
while it follows Omarchy and {"mode":16} once it does not, which is a stop and never a free number.
Every change goes through flea --ui-state, which takes a lock, checks the value and merges it, so a
setting written here never overwrites one written somewhere else, and a change it could not save is
reported in the status bar rather than lost quietly. A value this build does not recognise falls back
on its own without disturbing the rest of the file, and deleting the file puts every section back on
its default.
Every mark Flea draws shares one edge, taken from the Omarchy brand spiral: lucide's 24 unit grid and stroke conventions, but square caps, mitered joins, and every rounded corner baked into a path replaced with a hard one. Genuine curves stay, because a circle is not a softened corner. Music note heads are squares rather than circles, which is the set's tell at 16 px.
That specimen is generated by tools/flea-glyph-sheet from
ui/js/Icons.js, the one path table in the tree: 47 marks, one d string each,
drawn here at the app's own stroke of 1.5. Re-run the tool rather than editing the sheet. Nothing is added to it
until a surface actually draws it. Colour never comes from this language either. Every mark takes a
palette role from the live Omarchy theme rather than a brand hex, so the whole set recolours when
the desktop does. The only literal colours anywhere in the UI are the five fallbacks in
Theme.qml's fallbackColor: three for roles the shell's own palette does not model, and two more
read before the palette has loaded. Omarchy themes are not authored to WCAG AA, so Flea keeps each
role's hue and walks its lightness until muted, symlink and executable clear 4.5:1 against the
ground they are drawn on. On a theme that already clears it nothing moves.
The spiral itself is reserved. It is the brand, not a row mark, and rows and menus never draw it. It appears in the empty state as a stroke on that same 24 unit grid, painting itself in from blank over 1.8 s above a caption that rotates through eight of them, and as a stroke crawl while a listing loads or a media preview buffers: the same path with a dash offset running around it, 1.6 s a cycle, never a rotation. A 150 ms hold-off sits in front of the crawl and a local directory settles inside it, so the crawl belongs to the slow sources: a network mount, a cold disk, or a search still walking a subtree, which is the shot below.
Tailscale and Dropbox are the one exception. Both are reproduced from their own artwork rather than recut, because the cut governs Flea's own glyphs and stops at somebody else's identity.
The terminal interface draws characters, not paths, so it does not inherit this set. Its mark column is one character per kind, upgrading to Nerd Font glyphs where the terminal has them.
- Omarchy, with Quickshell 0.3.1 or newer, for the UI.
- Rust to build the backend. This tree is built and tested against rustc/cargo 1.98.
bubblewrapforbwrapandutil-linuxforprlimit, both required for thumbnailing as described above. Everything else works without them.bsdtarand optionally7zfor archives; missing archive helpers are detected at startup.gvfs,gvfs-smb,gvfs-dnssdandgvfs-nfsprovide the network mount backends.- Qt's PDF and Multimedia modules for the preview column, which ship with Qt 6 on this platform.
cargo build --releaseThe binary lands at target/release/flea. Running it dispatches by mode:
flea [path] # terminal in a real terminal, a window everywhere else
flea --gui [path] # force the window
flea --tui [path] # force the terminal interface (not built yet)
flea --select <uri|path> # open the containing directory with that entry selected
flea --default [off] # become the desktop's default file manager and chooser, or stop--tui and --gui are mutually exclusive. With neither given, flea opens the terminal
interface only when both stdin and stdout are a real terminal, and opens the window
otherwise, which is the branch a .desktop launcher takes since it has no controlling
terminal. --default opens no window: it sets the inode/directory handler, Omarchy's two
file-manager keys and the file chooser, and off undoes every one of them, see
docs/install.md. --backend, --prewarm, --open and --terminal are the
internal modes the UI and the benchmarks drive directly; flea --open <path> is what Enter
on a file runs, and it hands the file to gio open and waits for it, while
flea --terminal <dir> is what the topbar's terminal button and Ctrl+T run, and it hands the
directory to xdg-terminal-exec --dir=. See AGENTS.md for their contract.
--select accepts either a file:// URI (percent-decoded) or a bare path, opens its
parent directory, and puts the cursor and the selection on that one entry once the
directory's first page of rows arrives. A target that does not exist still opens its
parent, with nothing selected: this is the one nautilus call site the Dropbox panel needs
(Service.qml reveals a synced file with nautilus --select). --print-target is a
test-only flag that resolves --select's pair and prints <parent> <target> instead of
opening a window; it exists so the resolution is testable without a display.
Run the development UI directly, bypassing the launcher, from the repository root:
QSG_RHI_BACKEND=vulkan FLEA_PATH="$HOME" FLEA_BIN="$PWD/target/release/flea" qs -p "$PWD/ui"flea --gui picks the renderer in src/gui.rs, so a direct qs launch has to name one itself;
without it Qt takes its own default. It is still not the launcher's window: src/gui.rs also sets
FLEA_RENDERER_AUTOMATIC=1 on that implicit choice, which is what arms the one OpenGL retry in
ui/shell.qml, and the line above deliberately leaves it unset so a scene-graph failure ends the
dev loop instead of detaching a second process behind it.
The backend protocol is newline-delimited JSON over the child process's stdin and stdout;
its exact wire shape is documented in docs/protocol.md.
keys.toml is the one key table and ui/js/Keymap.js is generated from it, so this list
and the application cannot disagree.
| Binding | Action |
|---|---|
j, k, Down, Up |
Move the cursor one row |
g, G |
First or last row |
| Ctrl-d, Ctrl-u | Half a viewport |
h, Backspace, Ctrl-Up |
Parent directory; Ctrl-Up under the Mac preset |
l |
Browse forward: enter a directory, preview a file, page a PDF, or activate a rail/share row; unused in media |
| Return, Enter, Ctrl-Down | Open a directory, or open a file with the desktop's handler; Ctrl-Down under the Mac preset |
| Space | Quick Look, and close it |
| Left, Right | Page a PDF, or seek in media |
v |
Toggle selection on the row |
J, K, Shift-Down, Shift-Up |
Extend the selection |
| Ctrl-a | Select all |
/ |
Filter the listing in place, in the list view |
f, Ctrl-f |
Search the subtree from home; Tab on the query line points the walk at the folder the pane is in instead, and the strip names the scope it will use |
o |
Reveal the result in its own directory |
y, x, p, Ctrl-c, Ctrl-x, Ctrl-v |
Copy, cut, paste; the chords are what Omarchy's Super-c, Super-x and Super-v deliver |
Y |
Copy the route of the directory being shown, for pasting into a terminal |
dd, Delete, Ctrl-Delete |
Trash. Two presses on the letter, one on the key; Ctrl-Delete under the Mac preset |
r, F2 |
Rename |
z, Ctrl-z |
Undo the last operation |
| Ctrl-Shift-n | New folder |
a, Ctrl-k |
Add a network mount; a from the rail, Ctrl-k from either view under the Mac preset |
| Ctrl-e | Eject the rail's device, or the removable volume the listing is inside |
| Ctrl-t | Open the configured terminal in the directory being shown; the topbar's terminal button is the same action |
| Ctrl-1, Ctrl-2, Ctrl-3 | List, columns, grid, under the Mac preset |
| Ctrl-Shift-1, Ctrl-Shift-2, Ctrl-Shift-3 | The same three, under the Windows preset |
| Ctrl-h | Show hidden files, under the Windows preset |
| Ctrl-Shift-+, Ctrl-Shift-- , Ctrl-Shift-0 | Text size up a stop, down a stop, back to following Omarchy |
,, Ctrl-, |
Open the settings panel, from either view |
m |
Open the context menu on the cursor row; in the rail, eject or unmount |
s, S |
Step the sort column, reverse the sort |
., Ctrl-Shift-. |
Show hidden files |
| Tab | Move focus between the rail and the view; on a search's query line it picks the scope instead |
t |
Open a new tab at the current folder |
w |
Close the current tab |
1 to 9 |
Switch to that tab |
| Escape | Cancel a search, clear the filter, drop the selection, clear the status line |
Finder hands land: every Cmd chord above is its Ctrl twin, added beside the vim key rather
than in its place, because Hyprland keeps Super for itself and Omarchy's own universal
clipboard delivers Super-c, Super-x and Super-v as the Ctrl chords. Two Finder conventions are
deliberately not matched: Enter opens rather than renames, because every Linux file manager
and the vim table open on Enter and the TUI shares this table (rename is r or F2), and Cmd-d
is not duplicate, because Ctrl-d already pages with Ctrl-u as its pair (duplicate is a menu
row). The keymap sheet writes a chord as ^c, and ^N with a capital means Ctrl-Shift.
j, k and the arrows stop at the first and the last row. Issue 27 asked for them to come round
instead, so "wrapAtEnds": true in ~/.local/state/flea/ui.json turns that on: a step taken from
an end wraps, one that merely overshoots from the middle still stops at the end it was heading for,
and J, K and the shifted arrows keep the clamp, because an extend that wrapped would take every
row between the two ends with it. It ships off, and there is no key and no settings row for it,
because another operator reported the same jump as a bug.
Clicking a column header sorts by it, and clicking the sorted column reverses it. Name,
Size and Date Modified are real orders and s steps through the three; the two metadata
orders stat every row of the directory first, across the cores, and keep nothing
afterwards, so a listing in name order pays none of it and
docs/protocol.md records what that pass measured and when. Mode and
Kind are not sort orders at all, so each of the two says so and the mark stays where it
was. Directories group ahead of files in every order, and in a filtered view as well.
A left click puts the cursor on a row and drops any other selection, and the second one opens
it, which is macOS's rule rather than single-click-to-open; on a search result the second click
takes you to the file instead, in its own directory with the row selected, which is what o
does from the keyboard. Ctrl-click adds a row to the selection and Shift-click
extends the selection to it, and neither ever opens anything whatever the tap count. Right
click moves the cursor to a row and opens the context menu there: raised inside a multi-row
selection it acts on all of it, and raised outside one the selection collapses to that row,
because every entry the menu draws describes the row under the pointer. m opens the same menu
under the cursor row; j and k step it, and it closes on Escape, on a click outside, on
scrolling, and when its action runs. The columns view's two neighbour columns are peeks with no cursor of
their own, so one click there shows a directory in the middle column, the way a column view
reveals rather than opens, and a file still waits for the second click. The rail is one
place a single click opens, as Finder's own sidebar does, and a parent segment of the path above
the listing is the other; the segment the pane is already in is not a target. keys.toml carries the whole table,
its [[pointer]] half included.
/ opens a filter strip under the column header and narrows the rows already listed as
you type: no walk, no round trip, and the sort order and the directories-first grouping
both survive it. Enter hands the keyboard back to the list with the filter standing, so
every action key works over the narrowed rows, and Escape clears it. A row the filter
hides leaves the selection with it, because a selection you cannot see is one you can act
on by accident. It filters the rows the pane holds, and says so in the strip when that is
less than the whole directory.
: and Ctrl+L open the path bar, and so does a double click on the path itself: the strip
above the listing becomes the line you type into, opening on the current directory with the
whole line selected, so a name typed straight away replaces it. A leading / is absolute, a
leading ~ is home, anything else is relative to the directory you are already in, and a
file:// URI pasted from another application is read as the path it names. Tab completes
against the directories one level down, growing the line as far as the names agree and adding
the separator when only one is left, so Tab, Tab, Tab walks a tree. Enter goes, Escape leaves
the pane where it was, and . typed as the first character of a name completes hidden
directories whether or not the listing is showing them.
t opens a new tab at the current folder, w closes the current one, and 1 through 9 switch.
The tab strip shows only when there are two or more, so a single listing keeps the chrome the
first paint uses. The last tab cannot close; the window still closes with the compositor's close
chord.
./tests/run-all.sh # every suite that needs only a shell, and both cargo profiles
cargo test # unit tests
./tests/js.sh # pure QML JavaScript helpers
./tests/protocol.sh # drives the built binary over real stdin
./tests/modes.sh # asserts the mode contract
./tests/ops.sh # file operations and the undo journal
./tests/archive.sh # archive listing, extract and compress
./tests/thumbs.sh # the release binary against the media fixture
./tests/sandbox.sh # the thumbnail jail and its refusals
./tests/uistate.sh # ui.json: the lock, the settle, the migration and a SIGKILL sweep
./tests/uiwriter.sh # ViewState's writer under a headless Quickshell
./tests/charts.sh # the README's own tables against the bench CSV
./tests/ui.sh # drives the real window
./tests/drag.sh # the internal drag, through a real pointer on uinput
./tests/bench.sh # the field bench harness itself
./tests/budget.sh # the file-budget tool
./tests/keymap-gen.sh # ui/js/Keymap.js still matches keys.toml
FLEA_PACKAGE_FILE=/path/to/flea.pkg.tar.zst ./tests/package.sh # real makepkg archive
./tools/flea-acceptance # the everything-works battery
./tools/flea-file-budget # the file budget, against this tree
./tools/flea-field-bench # the cold field run against the other file managers
./tools/flea-media-fixture # builds the 2,000 file media fixture
./tools/flea-bench-report # a field run's CSV as the tables in this README./tests/run-all.sh is the main headless command. It builds both cargo profiles unconditionally,
because seven suites drive the debug binary and thumbs.sh the release one, and an "is there a
binary" guard is satisfied by a stale one from an older commit. Cargo decides for itself whether a
rebuild is owed, so a current tree pays nothing for asking. A suite invoked directly still says so
and stops when it cannot find its binary, rather than reporting every case as a product failure. It
runs every suite that needs nothing but a shell, and reads each suite's own exit code, not a
pipeline's.
It then names the suites it cannot run and says what each needs: ui.sh the display, drag.sh the
display and a real pointer through uinput, picker.sh the display, a session bus and Flea
activatable as the chooser backend, bench.sh its own benchmark contract, package.sh a real
makepkg archive in FLEA_PACKAGE_FILE, and network-live.sh live share credentials and the
approved runtime bundle. A suite in neither of the two lists fails the runner, so one cannot go
uninvoked again. There is no CI, and PKGBUILD's check() runs cargo test --release --locked,
tests/js.sh and tests/keymap-gen.sh, which are the two that need no built binary.
tools/flea-acceptance derives its checklist at run time from the protocol document, the
key table, the context menu, the design canvas and the sidebar, so it cannot be smaller
than the product. tools/flea-sandbox-guard owns every destructive path in the tools
above: nothing writes or deletes outside a fixture root that carries its own marker file.
If this saved you an afternoon, you can buy me a coffee.
MIT, and the full text is in LICENSE.