[HN Gopher] Show HN: Lstr - A modern, interactive tree command w...
       ___________________________________________________________________
        
       Show HN: Lstr - A modern, interactive tree command written in Rust
        
       Hi HN,  (First time poster!)  I'm the author of `lstr`. I've always
       loved the classic Linux `tree` command for its simplicity, but I
       often found myself wanting more modern features like interactivity
       and Git integration. So, I decided to build my own version in Rust
       with a philosophy of being fast, minimalist, and interactive. It
       was also an excuse to help learn more about Rust\\!  Here's a quick
       look at the interactive mode:
       https://raw.githubusercontent.com/bgreenwell/lstr/main/asset...
       I've just released v0.2.0 with some features I think this community
       might find useful:                 * **Interactive TUI Mode:** You
       can launch it with `lstr interactive`. It allows for keyboard-
       driven navigation, expanding/collapsing directories, and opening
       files in your default editor.       * **Git Status Integration:**
       Using the `-G` flag, `lstr` will show the Git status of every file
       and directory right in the tree output.       * **Shell
       Integration:** This is my favorite feature. In interactive mode,
       you can press `Ctrl+s` to quit and have `lstr` print the selected
       path to stdout. This lets you pipe it into other commands or use it
       as a visual `cd`. For example, you can add this function to your
       `.bashrc`/`.zshrc`:         ```bash         lcd() {
       local selected_path             selected_path="$(lstr interactive
       -gG)"             if [[ -n "$selected_path" && -d "$selected_path"
       ]]; then                 cd "$selected_path"             fi
       }         ```         Then just run `lcd` to visually pick a
       directory and jump to it.       It also supports file-type icons
       (via Nerd Fonts), file sizes, permissions, and respects your
       `.gitignore`.  The project is open-source and I would love to get
       your feedback.  GitHub: https://github.com/bgreenwell/lstr
       Crates.io: https://crates.io/crates/lstr  Thanks for checking it
       out!
        
       Author : w108bmg
       Score  : 205 points
       Date   : 2025-06-18 02:07 UTC (20 hours ago)
        
 (HTM) web link (github.com)
 (TXT) w3m dump (github.com)
        
       | aystatic wrote:
       | Neat, looks like a combination of erdtree[0] and broot[1]. I use
       | both on a daily basis, are there any features that stand out from
       | the two?
       | 
       | [0]: https://github.com/solidiquis/erdtree [1]:
       | https://github.com/Canop/broot
        
       | drabbiticus wrote:
       | First off, the display looks great!
       | 
       | Second off, I didn't realize how deep the dep tree would be for
       | this type of program -- 141 total! So much of it is the url
       | crate, itself a dep of the git crate, but there's a bunch of
       | others too. I'm just getting into learning Rust -- is this
       | typical of Rust projects or perhaps typical of TUI projects in
       | general?
       | 
       | (EDIT to strikeout) ~~The binary is also 53M as a result whereas
       | /usr/sbin/tree is 80K on my machine -- not really a problem on
       | today's storage, but very roughly 500-1000x different in size
       | isn't nothing.~~
       | 
       | Maybe it's linking-related? I don't know how to check really.
       | 
       | (EDIT: many have pointed out that you can run `cargo build
       | --release` with other options to get a much smaller binary.
       | Thanks for teaching me!)
        
         | CGamesPlay wrote:
         | Likely needs features tuned. I compared Eza, similarly in Rust,
         | and it's 1.6 MiB compiled. Looking at the Cargo.toml, it
         | includes git2 with default-features = false.
         | https://github.com/eza-community/eza/blob/main/Cargo.toml
        
         | fabrice_d wrote:
         | You are probably looking at a debug build. On Linux, a release
         | build (cargo build -r) is ~4.3M, and down to ~3.5M once
         | stripped. This could be reduced further with some tricks
         | applied to the release build profile.
        
         | pveierland wrote:
         | Building in release:                 cargo build --release
         | du -sh ./target/release/lstr -> 4.4M
         | 
         | Building with other release options brings it down to 2.3M:
         | [profile.release]       codegen-units = 1       opt-level = "s"
         | lto = true       panic = "abort"       strip = "symbols"
        
           | cyann wrote:
           | I did some benchmarks on one of our CLI and found that `opt-
           | level = "z"` reduced the size from 2.68M to 2.28M, and shaved
           | 10% on the build time, worth a try.
           | 
           | I'll try with `panic = "abort"` for our next release, thanks
           | for the reminder.
        
         | JoshTriplett wrote:
         | > The binary is also 53M
         | 
         | That's a debug binary, and the vast majority of that is debug
         | symbols. A release build of this project is 4.3M, an order of
         | magnitude smaller.
         | 
         | Also, compiling out the default features of the git2 crate
         | eliminates several dependencies and reduces it further to 3.6M.
         | 
         | https://github.com/bgreenwell/lstr/pull/5
         | 
         | https://github.com/rust-lang/git2-rs/pull/1168
         | 
         | Stripping the binary further improves it to 2.9M, and some
         | further optimizations get it to 2.2M without any compromise to
         | performance. (You can get it smaller by optimizing for size,
         | but I wouldn't recommend that unless you really do value size
         | _more_ than performance.)
        
           | esafak wrote:
           | No offense, but 4.3MB is huge for what it does. Most shells
           | take less space than that! Where's all the bloat coming from?
        
             | have-a-break wrote:
             | I feel like that's just the result of having a native
             | package manager making natural bloat and a compiler which
             | hasn't had decades of work.
        
             | o11c wrote:
             | For reference, some statically-linked shells on my system:
             | 2288K   /bin/bash-static (per manual, "too big and too
             | slow")       1936K   /bin/busybox-static (including tools
             | not just the shell)       192K    /usr/lib/klibc/bin/mksh
             | 2456K   zsh-static
             | 
             | For comparison, some dynamically-linked binaries (some old)
             | 804K    ./bin/bash-3.2       888K    ./bin/bash-4.0
             | 908K    ./bin/bash-4.1       956K    ./bin/bash-4.2
             | 1016K   ./bin/bash-4.3       1092K   ./bin/bash-4.4
             | 1176K   ./bin/bash-5.0       1208K   ./bin/bash-5.1
             | 1236K   /bin/bash (5.2)       124K    /bin/dash       1448K
             | /bin/ksh93 (fattest when excluding libc!)       292K
             | /bin/mksh       144K    /bin/posh       424K    /bin/yash
             | 848K    /bin/zsh
             | 
             | (The reason I don't have static binaries handy is because
             | they no longer run on modern systems. As long as you aren't
             | using shitty libraries, dynamic binaries are more portable
             | and reliable, contrary to internet "wisdom".)
        
             | JoshTriplett wrote:
             | Among the _features_ it has: an interactive terminal GUI,
             | threaded parallel directory walking, and git repository
             | support. In around a thousand lines of code, total,
             | including tests, half of which is the GUI.
        
               | oguz-ismail wrote:
               | *TUI. Not GUI
        
             | koito17 wrote:
             | > Most shells take less space than that!
             | 
             | Most shells dynamically link to a runtime your OS provides
             | "for free". The 4.3 MiB binary in question is bundling the
             | Rust runtime and its dependencies.
             | 
             | For reference, a statically-compiled C++ "Hello, World" is
             | 2.2 MiB after stripping.                 % cat hello.nix
             | {         pkgs ? import <nixpkgs> { crossSystem =
             | "aarch64-linux"; }       }:
             | pkgs.stdenv.mkDerivation {         name = "hello-static";
             | src = pkgs.writeText "hello.cpp" ''           #include
             | <iostream>           int main() {             std::cout <<
             | "Hello, World!" << std::endl;             return 0;
             | }         '';         dontUnpack = true;
             | buildInputs = [ pkgs.glibc.static ];         buildPhase =
             | "$CXX -std=c++17 -static -o hello $src";
             | installPhase = "mkdir -p $out/bin; cp hello $out/bin/";
             | }              % nix-build hello.nix       ...
             | % wc -c result/bin/hello       2224640 result/bin/hello
        
               | esafak wrote:
               | 2.2MiB for "Hello, World"? I must be getting old...
               | 
               | The executable takes 33KB in C, 75KB in nim.
        
               | koito17 wrote:
               | By switching to e.g. musl, you can go down to a single
               | megabyte ;)
               | 
               | But in all seriousness, my example is quite cherrypicked,
               | since nobody will actually statically link glibc. And
               | even if they did, one can make use of link-time
               | optimization to remove lots of patches of unused code.
               | Note that this is the same strategy one would employ to
               | debloat their Rust binaries. (Use LTO, don't aggressively
               | inline code, etc.)
        
               | 3836293648 wrote:
               | Just a `puts("Hello world!")` with -Os statically linked
               | to musl is 22k
        
               | 3836293648 wrote:
               | We just have large standard libraries now
        
               | surajrmal wrote:
               | lto will remove most of it.
        
               | wahern wrote:
               | > Most shells dynamically link to a runtime your OS
               | provides "for free"
               | 
               | Rust binaries also dynamically link to and rely on this
               | runtime.
        
               | mtndew4brkfst wrote:
               | That's not intrinsically or pervasively true, although
               | it's not uncommon.
        
               | wahern wrote:
               | Unless the majority of Rust builds on Linux statically
               | link musl libc or use no_std, then it's pervasively true.
               | And it's true on most non-Linux targets, including the
               | BSDs and macOS. It's the same situation with Go.
        
               | 3836293648 wrote:
               | The only way to avoid it is to be on linux with no_std,
               | use musl statically or be on embedded. You cannot (or at
               | least are supposed to not be able to) link to glibc
               | statically and on every other OS you can only call
               | syscalls via the system libraries. (Well, technically you
               | can on most systems, it's just not stable across updates.
               | OpenBSD will actively block it though)
        
               | fuzztester wrote:
               | why did you embed the c++ code in the .nix file?
               | 
               | just to have everything in one file? how to show how to
               | do it with nix?
               | 
               | because it seem simpler to have a separate C++ file, and
               | a simple shell script or makefile to compile it.
               | 
               | e.g. although I could figure out roughly what the .nix
               | file does, many more people would know plain unix shell
               | than nix.
               | 
               | and where is $out defined in the .nix file?
        
               | AnthOlei wrote:
               | The nix file is besides the point - it gives you a
               | totally hermetic build environment. Not OP, but it's the
               | only way I know how to get gcc to use a static glibc. All
               | you should pay attention to is that it's using a static
               | glibc.
               | 
               | $out is a magic variable in nix that means the output of
               | the derivation - the directory that nix moves to its
               | final destination
        
               | fuzztester wrote:
               | thanks.
        
               | JoshTriplett wrote:
               | > Not OP, but it's the only way I know how to get gcc to
               | use a static glibc.                   /tmp$ gcc -O3
               | test.c -o test         /tmp$ ldd test          linux-
               | vdso.so.1 (0x00007f3d9fbfe000)          libc.so.6 =>
               | /lib/x86_64-linux-gnu/libc.so.6 (0x00007f3d9f9e8000)
               | /lib64/ld-linux-x86-64.so.2 (0x00007f3d9fc00000)
               | /tmp$ gcc -static -O3 test.c -o test         /tmp$ ldd
               | test          not a dynamic executable
        
               | fuzztester wrote:
               | >> Not OP, but it's the only way I know how to get gcc to
               | use a static glibc.
               | 
               | > /tmp$ gcc -static -O3 test.c -o test /tmp$ ldd test not
               | a dynamic executable
               | 
               | yes, that last line above means it's a statically linked
               | executable.
               | 
               | yes, i had a doubt about what the GP said, about their
               | nix way being the only way to create a statically linked
               | executable.
               | 
               | but I didn't remember all the details, because it's been
               | a while since I worked with C in depth (moved to Java,
               | Ruby, Python, etc.)(though I did a lot of that earlier,
               | even in pre-Linux years), so I didn't say anything else.
               | thanks, Josh Triplett for clarifying.
               | 
               | but one thing I do remember, is that static linking was
               | the only option in the beginning, at least on Unix, and
               | dynamic linking came only some time later.
               | 
               | when I started working on UNIX and C, there was no
               | dynamic linking at all, IIRC.
               | 
               | https://en.m.wikipedia.org/wiki/Static_library
               | 
               | ("dynamic linking" topic in above page links to the below
               | page in Wikipedia: )
               | 
               | https://en.m.wikipedia.org/wiki/Dynamic_linker
        
               | 3836293648 wrote:
               | I thought glibc had some hacks in it to prevent it from
               | working fully when statically linked? Is this just a myth
               | or outdated or only affects C/C++ or what?
        
               | fuzztester wrote:
               | ah, so nix has magic, eh?
               | 
               | then, nix to nix, i say.
               | 
               | let those who want to love it, love it.
               | 
               | for me: nein, nyet, non, nada, nako, nahi ... :)
               | 
               | that's "no" in german, russki ;), french, spanish,
               | marathi, hindi.
               | 
               | adios, etc. ...
        
               | fuzztester wrote:
               | and by the way, ignorant mindlessly downvoting dudes who
               | don't even bother to check if a comment is right or not,
               | can shove it up, and take a flying leap into Lake
               | Titicaca. they'll meet a lot of their brothers there,
               | like giant frogs.
               | 
               | from a Google search:
               | 
               | >Overview Lake Titicaca, straddling the border between
               | Peru and Bolivia in the Andes Mountains, is one of South
               | America's largest lakes and the world's highest navigable
               | body of water. Said to be the birthplace of the Incas,
               | it's home to numerous ruins. Its waters are famously
               | still and brightly reflective. Around it is Titicaca
               | National Reserve, sheltering rare aquatic wildlife such
               | as giant frogs.
               | 
               | :)
        
         | ethan_smith wrote:
         | Try `cargo build --release --no-default-features` to get a much
         | smaller binary (~5-10MB) - Rust statically links dependencies
         | but supports conditional compilation for optional features.
        
           | aystatic wrote:
           | Glancing at the Cargo.toml, the package doesn't define any
           | features anyways. `cargo b --no-default-features` only
           | applies to the packages you're building, not their
           | dependencies -- that would lead to very unpredictable
           | behavior
        
         | getcrunk wrote:
         | Great catch! Comments mentioned getting it down to ~2MB but
         | that's still humongous.
         | 
         | If you just think about how roughly (napkin math) 2MB can be
         | 100k loc, that's nuts
        
           | arlort wrote:
           | Is It though? You won't get it on an embedded device (maybe)
           | but you could install a thousand of these tools and barely
           | even notice the space being taken up on most machines
        
             | getcrunk wrote:
             | I think that's a lame argument. First because it's kind of
             | a fallacy. Size is absolute not relative to something.
             | Especially for software. No one thinks of software size
             | primarily in the context of their disk space.
             | 
             | Further I think everyone keeps getting larger and larger
             | memory because software keeps getting more and more
             | bloated.
             | 
             | I remember when 64gb iPhone was more than enough (I don't
             | take pictures so just apps and data) Now my 128 is getting
             | uncomfortable due to the os and app sizes. My next phone
             | likely will be a 256
        
               | dotancohen wrote:
               | So bloated software is motivating you to spend more for
               | the larger capacity phone?
               | 
               | What incentive does Apple have to help iOS devs get
               | package sizes down, then?
        
               | hnlmorg wrote:
               | I'm usually the first to complain about bloat but your
               | counterpoints to the GPs "lame arguments" are themselves,
               | fallacies.
               | 
               | > First because it's kind of a fallacy. Size is absolute
               | not relative to something. Especially for software. No
               | one thinks of software size primarily in the context of
               | their disk space.
               | 
               | That's exactly how most people think about file sizes.
               | 
               | When your disk is full, you don't delete the smallest
               | files first. You delete the biggest.
               | 
               | > Further I think everyone keeps getting larger and
               | larger memory because software keeps getting more and
               | more bloated.
               | 
               | RAM sizes have actually stagnated over the last decade.
               | 
               | > I remember when 64gb iPhone was more than enough (I
               | don't take pictures so just apps and data) Now my 128 is
               | getting uncomfortable due to the os and app sizes. My
               | next phone likely will be a 256
               | 
               | That's because media sizes increase, not executable
               | sizes.
               | 
               | And people do want higher resolution cameras, higher
               | definition videos, improved audio quality, etc. These are
               | genuinely desirable features.
               | 
               | Couple that with improved internet bandwidth allowing for
               | content providers to push higher bitrate media, however
               | the need to still locally cache media.
        
               | nicoburns wrote:
               | > That's because media sizes increase, not executable
               | sizes.
               | 
               | Part of it is app sizes on mobile. But it's apps in the
               | 200mb - 2gb range that are the problem, not ones that
               | single-digit megabytes.
        
               | hnlmorg wrote:
               | 200MB apps wouldn't even make a dent on a 64GB device.
               | 
               | The 2GB apps are usually so large because they include
               | high quality media assets. For example, Spotify will
               | frequently consumer multiple GBs of storage but the vast
               | majority of that is audio cache.
        
               | nicoburns wrote:
               | I currently have 355 apps installed on my phone, so if
               | they were all 200mb then they wouldn't fit on a 64GB
               | device.
               | 
               | I agree that the largest data use tends to be media
               | assets.
        
               | hnlmorg wrote:
               | I'm intrigued, how many of them are actual 3rd party apps
               | though? And how many are different layers around an
               | existing app or part of Apple / Googles base OS? The
               | latter, in fairness, consumes several GBs of storage too.
               | 
               | I'm not trying to dismiss your point here. Genuinely
               | curious how you've accumulated so many app installs.
        
               | nicoburns wrote:
               | It's an interesting question. Some of them are definitely
               | from the OS (either Google or Samsung).
               | 
               | Looking through at categories of app where I have
               | multiple, I'm seeing:
               | 
               | - Transport provider apps (Airlines, Trains, Buses, Taxis
               | etc)
               | 
               | - Parking payment apps
               | 
               | - Food delivery apps
               | 
               | - Hotel apps
               | 
               | - Payment apps
               | 
               | - Messaging / Video calling apps
               | 
               | - Banking apps
               | 
               | - Mapping apps
               | 
               | It's especially easy to accumulate a lot of apps if you
               | travel through multiple countries, as for a lot of these
               | apps you need different ones in different countries.
        
               | ghosty141 wrote:
               | > No one thinks of software size primarily in the context
               | of their disk space.
               | 
               | This is wrong. The reason why many old tools are so small
               | was because you had far less space. If you have a 20tb
               | harddrive you wouldn't care about whether ls took up 1kb
               | or 2mb, on a 1gb harddrive it matters/ed much more.
               | 
               | Optimization takes time, I'm sure if OP wanted he could
               | shrink the binary size by quite a lot but doing so has
               | its costs and nowadays its rarely worth paying that since
               | nobody even notices wether a program is 2kb or 2mb. It
               | doesn't matter anymore in the age of 1TB bootdrives.
        
               | pxc wrote:
               | Size may be absolute, but bigness and smallness are
               | inherently and inescapably relative.
        
           | vlovich123 wrote:
           | When you include the code for all the dependency features
           | this uses, you probably do end up close to 100k LoC net, no?
        
             | mtndew4brkfst wrote:
             | lib.rs has a nifty (and occasionally shocking) portrayal of
             | this on their crate pages.
             | 
             | https://lib.rs/crates/lstr
             | 
             | says for this one the deps clock in at: ~19-29MB ~487K SLoC
        
       | ipdashc wrote:
       | Seems quite cool! Though the demo gif with (what seem to be?)
       | broken icons is a bold choice :p
        
         | w108bmg wrote:
         | lol good catch, and I totally missed it
         | 
         | I used vhs to record the gif which must not run the script in
         | my native terminal! I'll have to see about fixing it!
        
           | yonatan8070 wrote:
           | I've seen a lot of people use asciinema to record and share
           | terminal recordings, it works quite well
        
             | diggan wrote:
             | You still end up having to turn it into a GIF if you want
             | it to autoplay on GitHub's markdown viewer, or video if you
             | want it to run on the page but require a click-to-play.
        
               | yonatan8070 wrote:
               | Huh, I though you could embed those into READMEs on
               | GitHub, but turns out you can't
        
             | nightpool wrote:
             | How would that solve the font problem? I feel like that
             | would only make the problem of having unsupported fonts
             | even worse.
        
           | wonger_ wrote:
           | One solution could be to use the docker version of vhs, and
           | edit the dockerfile to pull your desired font.
        
         | sdegutis wrote:
         | I didn't notice, I was too busy seeing how impressive and
         | useful this tool is.
         | 
         | And with fuzzy matching built in? Just amazing. Good job OP.
        
       | sdegutis wrote:
       | So after writing this to learn Rust, what are your thoughts on
       | Rust? What do you especially like and dislike about it, or what
       | were you surprised about?
        
         | w108bmg wrote:
         | I appreciate the ecosystem of packages that seem really well
         | maintained. I don't love the syntax and find Rust harder to
         | read and learn so far compared to something like golang (I'm
         | used to R which is not a compiled language but has a great dev
         | community).
         | 
         | I do love the compiler and support tools built into Cargo (fmt,
         | clippy, etc.).
        
           | sdegutis wrote:
           | That's been similar to my experience. The ecosystem is
           | extremely polished and smooth, the build tools and package
           | manager and IDE support, all of it. Especially compared to
           | C++ which I cuold barely get working here.
        
       | w108bmg wrote:
       | Really appreciate all the comments and useful feedback (first
       | Rust package). Especially ways to reduce the size of the binary!
        
       | rewgs wrote:
       | This looks awesome! Right up my alley.
       | 
       | Side-note: what theme are you using in the linked gif? It's right
       | in the middle of my two favorite themes, onedark and gruvbox.
        
       | berkes wrote:
       | I really love all the "modern" takes on classic tools by the Rust
       | community.
       | 
       | I'm using eza (aka exa), aliased as ls, which has "tree" built in
       | (aliased as lt), amongst others, as replacement for "ls" and it's
       | one of my biggest production boosts in daily commandline use.
       | Because eza has the tree built in, and the tree is also insanely
       | fast, I won't be needing this tool - yet. Maybe one day the
       | interactive mode will pull me over.
       | 
       | Congrats on releasing. And kudo's to how well you've released it:
       | solid README, good description, good-looking gifs with exactly
       | the right feature highlights
        
         | fer wrote:
         | On interactive mode "ls tree" tools, an interesting one is
         | broot.
         | 
         | https://github.com/Canop/broot
        
       | b0a04gl wrote:
       | crazy good, so it'll ignore showing files enlisted in gitignore
       | while listing?
        
       | dboreham wrote:
       | No async which is nice. TIL that Rayon has an almost Occam like
       | syntax:
       | https://github.com/bgreenwell/lstr/blob/44b9bbf118ca90558138...
       | although you subsequently went with a library that bundles
       | parallel filesystem traversal.
        
       | gorgoiler wrote:
       | Doing this in just over 1000 lines of code is really inspiring. I
       | was bracing myself for something quite a lot larger and,
       | honestly, gearing up to use that as an excuse not to delve
       | through the code. No such luck... good!
        
       ___________________________________________________________________
       (page generated 2025-06-18 23:01 UTC)