zeus-fm 0.1.2

Fast and flexible terminal file manager
Documentation
use tui::widgets::{ListState, ListItem, List, Borders, Block, StatefulWidget};
use tui::{Frame, backend::Backend};
use tui::{text::Span};
use tui::layout::{Rect};
use tui::buffer::{Buffer};
pub use tui::style::{Style, Color, Modifier};

use std::path::Path;
use std::iter::FromIterator;

use crate::zeuslib::utils::fs::FileSize;

#[derive(Clone)]
#[derive(Debug)]
pub struct FileListItem {
    pub path: String,
    pub marked: bool,
}


impl FileListItem {
    pub fn new(path: String) -> Self {
        Self {
            path,
            marked: false
        }
    }

    pub fn is_dir(&self) -> bool {
        let p = Path::new(&self.path);
        p.is_dir()
    }

    pub fn is_file(&self) -> bool {
        let p = Path::new(&self.path);
        p.is_file()
    }
    fn get_size_str(&self) -> String {
        let p = Path::new(&self.path);
        if p.is_dir() {
            String::from("DIR")
        } else if let Ok(meta) = p.metadata() {
            let l = meta.len();
            format!("{}", FileSize::from_total_bytes(l))
        } else {
            String::from("")
        }
    }

    fn get_style(&self) -> Style {
        if self.marked {
            Style::default()
                .fg(Color::LightBlue)
        } else {
            if self.is_dir() {
                Style::default()
                    .fg(Color::Red)
            } else {
                Style::default()
                    .fg(Color::White)
            }
        }
    }
    fn get_text(&self, width: u16) -> String {
        let base = if self.is_dir() {
            format!(" [{}]", self.path)  
        } else {
            format!(" {}", self.path)
        };

        let base = if self.marked {
            format!(">{}", base.trim_start())
        } else {
            base
        };

        let size_text = self.get_size_str();
        let width: usize = width.into();
        let pad_width = {
            let mut w = width;
            w -= base.len() + 1;
            w -= size_text.len() + 1;
            w
        };
        
        let padding = String::from_iter((0 .. pad_width).map(|_| ' '));

        format!("{}{}{}", base, padding, size_text)
    }

    fn order(a: &FileListItem, b: &FileListItem) -> std::cmp::Ordering {
        let a_dir = a.is_dir();
        let b_dir = b.is_dir();
        if a_dir == b_dir {
            if a.path < b.path {
                std::cmp::Ordering::Less
            } else if a.path > b.path {
                std::cmp::Ordering::Greater
            } else {
                std::cmp::Ordering::Equal
            }
        } else if a_dir && !b_dir {
            std::cmp::Ordering::Less
        } else if !a_dir && b_dir {
            std::cmp::Ordering::Greater
        } else {
            std::cmp::Ordering::Equal
        }
    }
}



#[allow(dead_code)]
#[derive(Clone)]
#[derive(Debug)]
pub struct FileList {
    pub state: ListState,
    pub items: Vec<FileListItem>,
    pub cursor_pos: usize,
    root: String,
}

impl StatefulWidget for FileList {
    type State = ListState;
    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State)
    {
        let w = area.width;
        let items: Vec<ListItem> = self.items
            .iter()
            .map(|x| {
                ListItem::new(Span::raw(x.get_text(w))).style(x.get_style())
            }).collect();

        let items = List::new(items)
            .block(Block::default().borders(Borders::ALL).title(self.root.clone()))
            .highlight_style(
                    Style::default()
                        .fg(Color::Yellow)
                        .add_modifier(Modifier::REVERSED),
                );
        items.render(area, buf, state);
    }
}

#[allow(dead_code)]
impl FileList {
    pub fn new(root: &str) -> Self {
        Self::with_items(&[], root)
    }

    pub fn with_items(items: &[FileListItem], root: &str) -> Self {
        Self {
            state: ListState::default(),
            items: Vec::from(items),
            root: String::from(root),
            cursor_pos: 0,
        }
    }

    pub fn set_items(&mut self, items: &[FileListItem]) {
        self.items = Vec::from(items);
    }

    pub fn select(&mut self, index: usize) {
        self.state.select(Some(index));
        self.cursor_pos = index;
    }
    pub fn selected(&self) -> Option<usize> {
        self.state.selected()
    }

    pub fn refresh_list(&mut self) {
        let p = Path::new(&self.root);
        if !p.is_dir() {
            return;
        }

        self.items.clear();
        if let Ok(dir) = p.read_dir() {
            for entry in dir {
                if let Ok(entry) = entry {
                    let p = entry.path();
                    if let Some(p) = p.to_str() {
                        self.items.push(FileListItem::new(String::from(p)));
                    }
                }
            }
        }
        self.items.sort_by(FileListItem::order);
    }

    pub fn set_root(&mut self, root: &str) {
        self.root = String::from(root);
        self.refresh_list();
    }

    pub fn next(&mut self) {
        let i = match self.state.selected() {
            Some(i) => {
                if i >= self.items.len() - 1 {
                    0
                } else {
                    i + 1
                }
            }
            None => 0,
        };
        self.state.select(Some(i));
        self.cursor_pos = i;
    }

    pub fn previous(&mut self) {
        let i = match self.state.selected() {
            Some(i) => {
                if i == 0 {
                    self.items.len() - 1
                } else {
                    i - 1
                }
            }
            None => 0,
        };
        self.state.select(Some(i));
        self.cursor_pos = i;
    }

    pub fn unselect(&mut self) {
        self.state.select(None);
    }

    pub fn draw<B: Backend>(&mut self, f: &mut Frame<B>, size: &Rect) {
        f.render_stateful_widget(self.clone(), *size, &mut self.state);
    }
}