colorls 1.0.11

A fast, production-ready Rust rewrite of colorls: a beautified ls with icons, colors, git status and tree view.
use std::path::Path;

use crate::cli::Cli;
use crate::entry::{read_dir_entries, FileEntry};
use crate::git::GitRepoInfo;
use crate::icons::{entry_color, icon_for};
use crate::render::RenderCtx;
use crate::sorter::sort_entries;

const BRANCH: &str = "\u{251c}\u{2500}\u{2500} "; // "├── "
const LAST_BRANCH: &str = "\u{2514}\u{2500}\u{2500} "; // "└── "
const PIPE: &str = "\u{2502}   "; // "│   "
const SPACE: &str = "    ";

pub fn render(root: &Path, cli: &Cli, ctx: &RenderCtx, git: Option<&GitRepoInfo>) {
    let max_depth = cli.tree.unwrap_or(3);
    let name = root
        .file_name()
        .map(|n| n.to_string_lossy().to_string())
        .unwrap_or_else(|| root.to_string_lossy().to_string());
    crate::oprintln!(
        "{}",
        crate::colors::paint(&name, "dir", ctx.theme, ctx.color_enabled)
    );
    walk(root, "", 1, max_depth, cli, ctx, git);
}

fn walk(
    dir: &Path,
    prefix: &str,
    depth: usize,
    max_depth: usize,
    cli: &Cli,
    ctx: &RenderCtx,
    git: Option<&GitRepoInfo>,
) {
    if depth > max_depth {
        return;
    }

    let mut entries: Vec<FileEntry> = match read_dir_entries(dir) {
        Ok(e) => e,
        Err(e) => {
            eprintln!(
                "{}: {}: {}",
                crate::util::PROG_NAME.as_str(),
                dir.display(),
                e
            );
            return;
        }
    };

    if !cli.show_hidden() {
        entries.retain(|e| !e.is_hidden());
    }

    sort_entries(&mut entries, cli);

    let last_idx = entries.len().saturating_sub(1);
    for (i, entry) in entries.iter().enumerate() {
        let is_last = i == last_idx;
        let connector = if is_last { LAST_BRANCH } else { BRANCH };

        let icon = if ctx.icons_enabled {
            format!("{} ", icon_for(entry, ctx.theme))
        } else {
            String::new()
        };

        let mut label = entry.name.clone();
        if entry.is_dir {
            label.push('/');
        }
        if let Some(target) = &entry.symlink_target {
            label.push_str(" -> ");
            label.push_str(&target.to_string_lossy());
        }

        let color = entry_color(entry, ctx.theme);
        let colored_label = crate::colors::paint_color(&label, color, ctx.color_enabled);

        let git_glyph = if cli.git_status {
            let status = git.map(|g| g.status_for(&entry.path));
            match status {
                Some(s) => format!(
                    "{} ",
                    crate::colors::paint(s.glyph(), s.category(), ctx.theme, ctx.color_enabled)
                ),
                None => String::new(),
            }
        } else {
            String::new()
        };

        let branch = crate::colors::paint(connector, "tree_branch", ctx.theme, ctx.color_enabled);
        crate::oprintln!("{}{}{}{}{}", prefix, branch, git_glyph, icon, colored_label);

        if entry.is_dir && !entry.is_symlink {
            let next_prefix = format!("{}{}", prefix, if is_last { SPACE } else { PIPE });
            walk(
                &entry.path,
                &next_prefix,
                depth + 1,
                max_depth,
                cli,
                ctx,
                git,
            );
        }
    }
}