Canopy! It’s a fast rust CLI that prints directory trees. Just something i dove into when getting back into Rust!


screenshots + repo!

i wanted a tree‑like tool in rust that’s small, fast, and kinda fun/entertaining to mess with. along the way, i hit a lot of interesting roadblocks: (ownership, error handling, unicode widths, interactive terminal UI). this repo is just for fun/hobby, and maybe a tiny learning playground for you!



It has..



unicode tree drawing

– i underestimated how annoying it is to line up box-drawing chars without something breaking when the path names are weird. i ended up manually building each “branch” and keeping track of whether the current node was the last child so that the vertical lines stop correctly!



sorts files & directories + supports filters!

– mixing recursion with sorting and filtering in rust iterators forced me to rethink my borrow/ownership strategy. i rewrote the traversal multiple times!



recursive by default

– walking directories recursively meant dealing with large trees and “what happens if file count is huge?” also taught me to keep things efficient and not block the UI!



good error handling + clean codebase (i try)

– rust’s error model forced me to deal with a lot of “things that can go wrong”: unreadable directory, permissions, broken symlinks. i learned the value of thiserror, anyhow, and good context.



interactive mode (kinda like vim/nano)

– stepping into terminal UI mode made me realize that making “simple” interactive behaviour is way more work than add‑feature mode. handling input, redraws, state transitions got tough kinda quick.



1. building the tree

build_tree() recursively walks a directory, then collects entries, then filters hidden files, then applies optional glob filters, and sorts them. the recursive depth is handled by decreasing max_depth!

fn build_tree(path: &Path, max_depth: Option, show_hidden: bool, filter: Option<&str>) -> std::io::Result {
    ...
    for entry in entries {
        let child = if is_dir && max_depth.map_or(true, |d| d > 0) {
            let new_depth = max_depth.map(|d| d - 1);
            build_tree(&entry.path(), new_depth, show_hidden, filter)?
        } else {
            TreeNode { ... }
        };
        children.push(child);
    }
    ...
}

Enter fullscreen mode

Exit fullscreen mode

if you don’t understand this, recursion, filtering, and sorting can get really tricky with ownership stuff. i went through a few versions until it compiled cleanly



2. printing trees

print_tree() adds branches, colors, and size info, added in v2

let connector = if is_last { "└── " } else { "├── " };
println!("{}{}{}{}", prefix, connector, icon_colored, name_colored);
Enter fullscreen mode

Exit fullscreen mode

stay careful with prefixes and “last child” logic, otherwise your tree looks broken! using coloring via colored crate made it easy to give context (dirs are blue, big files are red)



3. collapsing single-child directories

collapse_tree() merges dirs with only one child to get rid of clutter.

if new_children.len() == 1 && new_children[0].is_dir {
    TreeNode {
        name: format!("{}/{}", name, child.name),
        children: child.children,
        ...
    }
} ...
Enter fullscreen mode

Exit fullscreen mode

basically recursion is beautiful until you try to mutate the structure while walking it lol



4. its interactive TUI

this was one of the bigger challenges, but made a solution using ratatui and crossterm to let you navigate dirs! arrow keys move selection, enter opens files, left/backspace goes up.. separating state (current_path, entries, selected) made life much easier!



how to try it or view its source:



building manually!

git clone https://github.com/hnpf/canopy
cd canopy
cargo build --release
./target/release/virex-canopy [path] [options]
Enter fullscreen mode

Exit fullscreen mode



its that simple!



some examples..

uses current dir, 2directories down + view hidden files:

virex-canopy . --depth 2 --hidden
Enter fullscreen mode

Exit fullscreen mode

Filtering rust files + interactive mode:

virex-canopy /home/user/projects --filter "*.rs" --interactive
Enter fullscreen mode

Exit fullscreen mode

Export path to json:

virex-canopy ./ --json
Enter fullscreen mode

Exit fullscreen mode



for trying it

cargo install virex-canopy         # newest ver

Enter fullscreen mode

Exit fullscreen mode



what you can probably learn from it!

  • recursive tree traversal in rust..

  • sorting, filtering, and handling hidden files..

  • managing ownership and borrowing in a real project

  • maybe learning how to make interactive tui

  • exporting data to JSON or CSV



notes / fun facts

  • i started this as a tiny side project, and ended up learning a lot about rust error handling & UI design

  • treenode struct is fully serializable, making testing/export easy

  • more stuff: handling symlinks, very large files, unicode branch alignment



feedback and github contributions are really welcome, especially stuff like “this code is…” or “there’s a cleaner way to do X”. this is just a fun side project for me and i’m always down to learn more rust 🙂



Source link

Leave a Reply

Your email address will not be published. Required fields are marked *