[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)