Canger

A VIM-inspired C# port of the ranger file manager for the console.

What it is

ranger is a terminal file manager with vi-style key bindings. It shows a directory as a set of columns — the path leading to where you are, the listing itself, and a preview of whatever the cursor sits on. Moving right enters a directory or opens a file; moving left goes back up. Almost everything is a command, and almost every key is bound to one.

Canger is that program, rewritten from Python to C# on .NET 10.

The target is a 1-to-1 feature match: the same key bindings, the same commands, the same settings, the same configuration file syntax. If a key does something in ranger and something else in Canger, that is a bug. It ships with rifle, ranger’s file launcher, unchanged in contract — so existing scope.sh preview scripts work without modification.

Why port it at all

Because a port is the honest way to test an argument.

It is easy to claim that a language with static types and real threads produces better software than one without. It is harder to take a mature, well-liked program written in the second and rebuild it in the first, feature for feature, and then say what actually changed. Ranger is a good subject: fifteen years of accumulated behaviour, a large surface area, and a user base that will notice immediately if a key does the wrong thing.

Every subsystem has been ported.

Settings 87 — ranger’s 83, plus four of its own, with global, path-regex and tag scopes
Commands 118 built in, plus whatever commands.cs adds
Key bindings 295 browser, 36 console, 35 pager, 33 task view, 29 devices
Colour contexts 82, matching ranger’s names exactly
View modes miller, multipane
VCS backends git, hg, svn, bzr
Tests 2,240

Eleven things it does that ranger does not

The port was the goal; these came out of doing it properly. A few of the more useful ones:

Reflink copies. On btrfs, XFS and bcachefs, a same-filesystem copy becomes a copy-on-write clone via ioctl(FICLONE) — instant, and costing no extra space. Failing that it tries copy_file_range(2), and failing that a buffered copy.

Copy progress with an ETA for the whole queue. Percentage, bytes, live throughput and time remaining across everything outstanding, rather than just the file currently moving. Paste two films and the bar runs once from nothing to full, instead of restarting when the first finishes.

Removable drives. <F9> lists what is plugged in, with mount, unmount, unlock, lock and safely-remove — the last of which unmounts everything on the drive, locks what is encrypted and cuts the power, stopping at the first step that fails. It all goes through udisksctl, so a drive mounted here behaves exactly like one mounted from a desktop file manager. Ranger has no equivalent.

It notices changes made outside it. A chmod, chown or write from another terminal does not touch a directory’s modification time, and that is all either program watches — so permissions, owner, size and date on screen stayed as first read, often for an entire session. Canger re-reads the rows that are on screen, which costs the same in a directory of twenty thousand entries as in one of twenty.

Version control that works where ranger’s does not. Ranger runs chg unconditionally for Mercurial; Canger falls back to hg. Ranger’s Bazaar commit line never appears at all, because its own parser requires a newline it has already stripped. Both programs’ Subversion and Bazaar parsers read trailing prose as filenames, inventing subpaths that cannot exist. All four backends were checked by running ranger’s Python and Canger’s C# over the same repositories and diffing the results.

Archives that report as they run. Unpacking measures against what comes out, not the file on disk — the uncompressed size of a .gz, .lz or .xz is recorded in the file itself and read from a few bytes at the end, so a 4 KB archive holding 27 MB does not reach 100% in the first second and sit there. Where a tool can say nothing, the bytes written are shown with no bar, because a bar drawn from a guessed compression ratio would lie.

Design goals

  • Match ranger’s behaviour, and be able to say where in ranger’s source each behaviour comes from
  • Modern C#, not transliterated Python — explicit types, real exceptions, dependency injection
  • Extensible in C#: commands.cs and plugins/*.cs are compiled at startup with Roslyn
  • Fast enough that the interface never makes you wait
  • Testable without a terminal or a real filesystem

Status and honest gaps

Canger is usable day to day, and is used that way — though it is young, first committed in September 2026, and has not yet been through the kind of exposure that finds the last of the bugs.

Five of ranger’s eight image protocols (w3m, iterm2, sixel, terminology, urxvt) are not implemented and fall back to no image. Only linux-x64 is built, though CANGER_RID will cross-compile. It is Linux-only and framework-dependent by necessity: the plugin host compiles C# at startup with Roslyn, which needs a JIT, so NativeAOT is not an option. TODO.md in the repository is the honest record of what is done, what was measured, and what is known to be missing.

Released under the GNU General Public Licence v3.0. Releases carry a .deb, an AppImage and two tarballs.