git-igitt 0.1.21

Interactive Git terminal application to browse and visualize Git history graphs arranged for your branching model
Documentation
use crate::app::App;
use git2::Repository;
use std::io::Error;
use std::path::PathBuf;
use tui::widgets::ListState;

pub struct FileDialog<'a> {
    pub title: &'a str,
    pub location: PathBuf,
    pub selection: Option<PathBuf>,
    pub dirs: Vec<(String, bool)>,
    pub error_message: Option<String>,
    pub color: bool,
    pub state: ListState,
    pub previous_app: Option<App>,
}

impl<'a> FileDialog<'a> {
    pub fn new(title: &'a str, color: bool) -> Result<FileDialog<'a>, Error> {
        Ok(FileDialog {
            title,
            location: std::env::current_dir()?,
            selection: None,
            dirs: vec![],
            error_message: None,
            color,
            state: ListState::default(),
            previous_app: None,
        })
    }

    pub fn fwd(&mut self, steps: usize) {
        let i = match self.state.selected() {
            Some(i) => std::cmp::min(i.saturating_add(steps), self.dirs.len().saturating_sub(1)),
            None => 0,
        };
        self.state.select(Some(i));
    }

    pub fn bwd(&mut self, steps: usize) {
        let i = match self.state.selected() {
            Some(i) => i.saturating_sub(steps),
            None => 0,
        };
        self.state.select(Some(i));
    }

    pub fn on_up(&mut self, is_shift: bool) {
        let step = if is_shift { 10 } else { 1 };
        self.bwd(step)
    }

    pub fn on_down(&mut self, is_shift: bool) {
        let step = if is_shift { 10 } else { 1 };
        self.fwd(step)
    }

    pub fn on_left(&mut self) -> Result<(), String> {
        if let Some(par) = self.location.parent() {
            let temp_path = self.location.clone();
            let prev = self.location.clone();
            let path = PathBuf::from(par);
            self.location = path.clone();
            match self.selection_changed(Some(prev)) {
                Ok(_) => {}
                Err(err) => {
                    self.location = temp_path;
                    self.error_message = Some(format!(
                        "Problem entering directory {}\n{}",
                        path.display(),
                        err
                    ));
                }
            };
        }
        Ok(())
    }

    pub fn on_right(&mut self) -> Result<(), String> {
        if let Some(sel) = self.state.selected() {
            if sel == 0 {
                return self.on_left();
            }
            let temp_path = self.location.clone();
            let file = &self.dirs[sel];
            let mut path = PathBuf::from(&self.location);
            path.push(&file.0);
            self.location = path.clone();
            match self.selection_changed(None) {
                Ok(_) => {}
                Err(err) => {
                    self.location = temp_path;
                    self.error_message = Some(format!(
                        "Problem entering directory {}\n{}",
                        path.display(),
                        err
                    ));
                }
            };
        }
        Ok(())
    }

    pub fn on_enter(&mut self) {
        if let Some(sel) = self.state.selected() {
            let file = &self.dirs[sel];
            let mut path = PathBuf::from(&self.location);
            path.push(&file.0);
            self.selection = Some(path);
        }
    }

    pub fn selection_changed(&mut self, prev_location: Option<PathBuf>) -> Result<(), String> {
        self.dirs = std::fs::read_dir(&self.location)
            .map_err(|err| err.to_string())?
            .filter_map(|path| match path {
                Ok(path) => {
                    if path.path().is_dir() {
                        let is_repo = Repository::open(path.path()).is_ok();
                        path.path()
                            .components()
                            .next_back()
                            .and_then(|c| c.as_os_str().to_str().map(|s| (s.to_string(), is_repo)))
                    } else {
                        None
                    }
                }
                Err(_) => None,
            })
            .collect();
        self.dirs.insert(0, ("..".to_string(), false));

        if self.dirs.is_empty() {
            self.state.select(None);
        } else if let Some(prev) = prev_location {
            if let Some(prev_index) = prev
                .components()
                .next_back()
                .and_then(|comp| comp.as_os_str().to_str())
                .and_then(|dir| self.dirs.iter().position(|d| d.0 == dir))
            {
                self.state.select(Some(prev_index));
            } else {
                self.state.select(Some(0));
            }
        } else {
            self.state.select(Some(0));
        }
        Ok(())
    }

    pub fn set_error(&mut self, msg: String) {
        self.error_message = Some(msg);
    }
    pub fn clear_error(&mut self) {
        self.error_message = None;
    }
}