lla 0.3.7

A lightweight ls replacement
use super::FileFormatter;
use crate::error::Result;
use crate::plugin::PluginManager;
use crate::theme::{self, ColorValue};
use crate::utils::color::{self, colorize_file_name, colorize_file_name_with_icon};
use crate::utils::icons::format_with_icon;
use chrono::{DateTime, Local};
use colored::*;
use lla_plugin_interface::proto::DecoratedEntry;
use std::collections::BTreeMap;
use std::path::Path;
use std::time::{Duration, UNIX_EPOCH};

pub struct RecursiveFormatter {
    pub show_icons: bool,
}

impl RecursiveFormatter {
    pub fn new(show_icons: bool) -> Self {
        Self { show_icons }
    }

    fn format_date(timestamp: u64) -> ColoredString {
        let datetime = UNIX_EPOCH + Duration::from_secs(timestamp);
        let datetime: DateTime<Local> = DateTime::from(datetime);
        let date_str = datetime.format("%Y-%m-%d %H:%M").to_string();
        let color = theme::color_value_to_color(&ColorValue::Named("bright black".to_string()));
        date_str.color(color)
    }

    fn get_header_color() -> Color {
        let theme = color::get_theme();
        theme::color_value_to_color(&theme.colors.directory)
    }

    fn get_separator_color() -> Color {
        theme::color_value_to_color(&ColorValue::Named("bright black".to_string()))
    }

    fn print_entry(&self, entry: &DecoratedEntry, plugin_manager: &mut PluginManager) {
        if let Some(metadata) = &entry.metadata {
            let date = Self::format_date(metadata.modified);
            let path = Path::new(&entry.path);
            let colored_name = colorize_file_name(path).to_string();
            let name = colorize_file_name_with_icon(
                path,
                format_with_icon(path, colored_name, self.show_icons),
            );

            print!("{:>16} │ {}", date, name);

            let plugin_fields = plugin_manager.format_fields(entry, "recursive").join(" ");
            if !plugin_fields.is_empty() {
                print!(" {}", plugin_fields);
            }
            println!();
        }
    }

    fn get_parent_path(path: &str) -> String {
        Path::new(path)
            .parent()
            .and_then(|p| p.to_str())
            .unwrap_or(".")
            .to_string()
    }
}

impl FileFormatter for RecursiveFormatter {
    fn format_files(
        &self,
        files: &[DecoratedEntry],
        plugin_manager: &mut PluginManager,
        _depth: Option<usize>,
    ) -> Result<String> {
        if files.is_empty() {
            return Ok(String::new());
        }

        let mut groups: BTreeMap<String, Vec<&DecoratedEntry>> = BTreeMap::new();

        for file in files {
            let parent = Self::get_parent_path(&file.path);
            groups.entry(parent).or_default().push(file);
        }

        let single_group = groups.len() == 1;

        for (parent, entries) in &groups {
            if !(single_group && parent == ".") {
                println!("\n{}", parent.color(Self::get_header_color()).bold());
                println!("{}", "".repeat(40).color(Self::get_separator_color()));
            }

            for entry in entries {
                self.print_entry(entry, plugin_manager);
            }
        }
        Ok(String::new())
    }
}