lla 0.5.11

Blazing Fast and highly customizable ls Replacement with Superpowers
use super::FileFormatter;
use crate::plugin::PluginManager;
use crate::utils::color::colorize_file_name;
use crate::utils::icons::format_with_icon;
use crate::{error::Result, utils::color::colorize_file_name_with_icon};
use lla_plugin_interface::proto::DecoratedEntry;
use std::path::Path;
use terminal_size::{terminal_size, Width};
use unicode_width::UnicodeWidthStr;

pub struct GridFormatter {
    pub show_icons: bool,
    pub grid_ignore: bool,
    pub max_width: usize,
}

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

        let term_width = if self.grid_ignore {
            self.max_width
        } else {
            terminal_size()
                .map(|(Width(w), _)| w as usize)
                .unwrap_or(80)
        };

        let mut formatted_entries = Vec::with_capacity(files.len());
        let mut max_width = 0;

        for file in files {
            let path = Path::new(&file.path);
            let colored_name = colorize_file_name(path).to_string();
            let name_with_icon = colorize_file_name_with_icon(
                path,
                format_with_icon(path, colored_name, self.show_icons),
            )
            .to_string();
            let plugin_fields = plugin_manager.format_fields(file, "grid").join(" ");
            let total_str = if plugin_fields.is_empty() {
                name_with_icon.clone()
            } else {
                format!("{} {}", name_with_icon.clone(), plugin_fields)
            };
            let width =
                String::from_utf8_lossy(&strip_ansi_escapes::strip(&total_str).unwrap_or_default())
                    .width();
            max_width = max_width.max(width);
            formatted_entries.push((name_with_icon, plugin_fields));
        }

        let column_width = max_width + 2;
        let num_columns = std::cmp::max(1, term_width / column_width);

        let mut output = String::new();
        let mut current_col = 0;

        for (colored_name, plugin_fields) in formatted_entries {
            let entry = if plugin_fields.is_empty() {
                colored_name
            } else {
                format!("{} {}", colored_name, plugin_fields)
            };

            if current_col >= num_columns {
                output.push('\n');
                current_col = 0;
            }

            output.push_str(&entry);
            if current_col < num_columns - 1 {
                let stripped_bytes = strip_ansi_escapes::strip(&entry).unwrap_or_default();
                let stripped = String::from_utf8_lossy(&stripped_bytes);
                let padding = column_width - stripped.width();
                output.push_str(&" ".repeat(padding));
            }

            current_col += 1;
        }

        Ok(output)
    }
}