joshuto 0.9.2

Terminal file manager inspired by ranger
use std::io;
use std::path;

use crate::config::AppMimetypeEntry;
use crate::context::{AppContext, QuitType};
use crate::error::{JoshutoError, JoshutoErrorKind, JoshutoResult};
use crate::ui::views::TuiTextField;
use crate::ui::TuiBackend;

use super::change_directory;

use crate::MIMETYPE_T;

pub fn get_options<'a>(path: &path::Path) -> Vec<&'a AppMimetypeEntry> {
    let mut options: Vec<&AppMimetypeEntry> = Vec::new();
    if let Some(file_ext) = path.extension() {
        if let Some(file_ext) = file_ext.to_str() {
            let ext_entries = MIMETYPE_T.app_list_for_ext(file_ext);
            options.extend(ext_entries);
        }
    }
    options
}

pub fn open(context: &mut AppContext, backend: &mut TuiBackend) -> JoshutoResult<()> {
    let config = context.config_ref();

    if let Some(entry) = context
        .tab_context_ref()
        .curr_tab_ref()
        .curr_list_ref()
        .and_then(|s| s.curr_entry_ref())
    {
        if entry.file_path().is_dir() {
            let path = entry.file_path().to_path_buf();
            change_directory::cd(path.as_path(), context)?;
        } else {
            let paths = context
                .tab_context_ref()
                .curr_tab_ref()
                .curr_list_ref()
                .map_or(vec![], |s| s.get_selected_paths());

            if paths.is_empty() {
                return Err(JoshutoError::new(
                    JoshutoErrorKind::Io(io::ErrorKind::NotFound),
                    String::from("No files selected"),
                ));
            }
            let files: Vec<&std::ffi::OsStr> = paths.iter().filter_map(|e| e.file_name()).collect();

            let options = get_options(paths[0].as_path());

            if context.args.choosefiles.is_some() {
                context.quit = QuitType::ChooseFiles;
            } else if !options.is_empty() {
                if options[0].get_fork() {
                    options[0].execute_with(files.as_slice())?;
                } else {
                    backend.terminal_drop();
                    let res = options[0].execute_with(files.as_slice());
                    backend.terminal_restore()?;
                    res?;
                }
            } else if config.xdg_open {
                backend.terminal_drop();
                open::that(paths[0].as_path())?;
                backend.terminal_restore()?;
            } else {
                open_with_helper(context, backend, options, files)?;
            }
        }
    }
    Ok(())
}

pub fn open_with_helper<S>(
    context: &mut AppContext,
    backend: &mut TuiBackend,
    options: Vec<&AppMimetypeEntry>,
    files: Vec<S>,
) -> std::io::Result<()>
where
    S: AsRef<std::ffi::OsStr>,
{
    const PROMPT: &str = "open_with ";

    let user_input: Option<String> = {
        context.flush_event();

        let menu_options: Vec<String> = options
            .iter()
            .enumerate()
            .map(|(i, e)| format!("  {} | {}", i, e))
            .collect();

        TuiTextField::default()
            .prompt(":")
            .prefix(PROMPT)
            .menu_items(menu_options.iter().map(|s| s.as_str()))
            .get_input(backend, context)
    };
    match user_input.as_ref() {
        Some(user_input) if user_input.starts_with(PROMPT) => {
            let user_input = &user_input[PROMPT.len()..];

            match user_input.parse::<usize>() {
                Ok(n) if n >= options.len() => Err(std::io::Error::new(
                    std::io::ErrorKind::InvalidData,
                    "option does not exist".to_string(),
                )),
                Ok(n) => {
                    let mimetype_entry = &options[n];
                    if mimetype_entry.get_fork() {
                        mimetype_entry.execute_with(files.as_slice())
                    } else {
                        backend.terminal_drop();
                        let res = mimetype_entry.execute_with(files.as_slice());
                        backend.terminal_restore()?;
                        res
                    }
                }
                Err(_) => {
                    let mut args_iter = user_input.split_whitespace();
                    match args_iter.next() {
                        Some(cmd) => {
                            backend.terminal_drop();
                            let res = AppMimetypeEntry::new(String::from(cmd))
                                .args(args_iter)
                                .execute_with(files.as_slice());
                            backend.terminal_restore()?;
                            res
                        }
                        None => Ok(()),
                    }
                }
            }
        }
        _ => Ok(()),
    }
}

pub fn open_with_interactive(
    context: &mut AppContext,
    backend: &mut TuiBackend,
) -> JoshutoResult<()> {
    let paths = context
        .tab_context_ref()
        .curr_tab_ref()
        .curr_list_ref()
        .map_or(vec![], |s| s.get_selected_paths());

    if paths.is_empty() {
        return Err(JoshutoError::new(
            JoshutoErrorKind::Io(io::ErrorKind::NotFound),
            String::from("No files selected"),
        ));
    }
    let files: Vec<&std::ffi::OsStr> = paths.iter().filter_map(|e| e.file_name()).collect();
    let options = get_options(paths[0].as_path());

    open_with_helper(context, backend, options, files)?;
    Ok(())
}

pub fn open_with_index(
    context: &mut AppContext,
    backend: &mut TuiBackend,
    index: usize,
) -> JoshutoResult<()> {
    let paths = context
        .tab_context_ref()
        .curr_tab_ref()
        .curr_list_ref()
        .map_or(vec![], |s| s.get_selected_paths());

    if paths.is_empty() {
        return Err(JoshutoError::new(
            JoshutoErrorKind::Io(io::ErrorKind::NotFound),
            String::from("No files selected"),
        ));
    }
    let files: Vec<&std::ffi::OsStr> = paths.iter().filter_map(|e| e.file_name()).collect();
    let options = get_options(paths[0].as_path());

    if index >= options.len() {
        return Err(JoshutoError::new(
            JoshutoErrorKind::Io(std::io::ErrorKind::InvalidData),
            "option does not exist".to_string(),
        ));
    }

    let mimetype_entry = &options[index];
    if mimetype_entry.get_fork() {
        mimetype_entry.execute_with(files.as_slice())?;
        Ok(())
    } else {
        backend.terminal_drop();
        let res = mimetype_entry.execute_with(files.as_slice());
        backend.terminal_restore()?;
        res?;
        Ok(())
    }
}