fpick 0.8.1

Interactive file picker
use std::str::Chars;

use crate::action_menu::{
    copy_path_to_clipboard, create_directory, create_file, delete_tree_node,
    execute_interactive_shell_operation, execute_shell_operation, get_file_details,
    read_file_content, rename_file, run_custom_command, run_custom_interactive_command, MenuAction,
    Operation,
};
use crate::app::App;
use crate::appdata::WindowFocus;
use crate::background::BackgroundEvent;
use crate::tree::TreeNode;
use crate::tui::Tui;

impl App {
    pub fn open_action_dialog(&mut self) {
        if self.child_tree_nodes.is_empty() {
            return;
        }
        self.window_focus = WindowFocus::ActionMenu;
        self.action_menu_cursor_y = 0;
    }

    pub fn close_action_dialog(&mut self) {
        self.window_focus = WindowFocus::Tree;
    }

    pub fn execute_dialog_action(&mut self, tui: &mut Tui) {
        let abs_path: String = match self.get_selected_abs_path() {
            Some(abs_path) => abs_path,
            None => return,
        };
        let tree_node: TreeNode = match self.get_selected_tree_node() {
            Some(tree_node) => tree_node,
            None => return,
        };
        let relative_path: Option<String> = self.make_relative_path(&abs_path);
        let is_directory = App::is_tree_node_directory(&tree_node);
        let current_dir_path: String = self.get_current_dir_abs_path();

        let action: &MenuAction = &self.known_menu_actions[self.action_menu_cursor_y];
        self.window_focus = WindowFocus::Tree;
        self.action_menu_operation = Some(action.operation.clone());
        match action.operation {
            Operation::ShellCommand { template } => {
                let result = execute_shell_operation(&abs_path, template);
                match result {
                    Err(err) => self.show_error(err.to_string()),
                    _ => {}
                }
            }
            Operation::InteractiveShellCommand { template } => {
                let result = execute_interactive_shell_operation(&abs_path, template, tui);
                match result {
                    Err(err) => self.show_error(err.to_string()),
                    _ => {}
                }
            }
            Operation::PickAbsolutePath => {
                self.pick_selected_node(Some(false));
            }
            Operation::PickRelativePath => {
                self.pick_selected_node(Some(true));
            }
            Operation::Rename => {
                let filename = abs_path.split('/').last().unwrap().to_string();
                self.open_action_menu_step2(format!("New name for {}", filename), filename);
            }
            Operation::CreateFile => {
                self.open_action_menu_step2(
                    format!("New file at {}", current_dir_path),
                    String::new(),
                );
            }
            Operation::CreateDir => {
                self.open_action_menu_step2(
                    format!("New directory at {}", current_dir_path),
                    String::new(),
                );
            }
            Operation::CustomCommand => {
                self.open_action_menu_step2(
                    format!("Run command at {}", current_dir_path),
                    format!("\"{}\"", abs_path),
                );
            }
            Operation::CustomInteractiveCommand => {
                self.open_action_menu_step2(
                    format!("Run interactive, external command at {}", current_dir_path),
                    format!("\"{}\"", abs_path),
                );
            }
            Operation::Delete => {
                let result = delete_tree_node(&tree_node, &abs_path);
                match result {
                    Err(err) => self.show_error(err.to_string()),
                    _ => {}
                }
            }
            Operation::CopyToClipboard { is_relative_path } => {
                let result = match is_relative_path {
                    true => copy_path_to_clipboard(&relative_path.unwrap()),
                    false => copy_path_to_clipboard(&abs_path),
                };
                match result {
                    Err(err) => self.show_error(err.to_string()),
                    _ => {}
                }
            }
            Operation::FileDetails => {
                let result = get_file_details(&abs_path, is_directory);
                match result {
                    Ok(info) => self.show_info(info),
                    Err(err) => self.show_error(err.to_string()),
                }
            }
            Operation::ViewContent => {
                if !is_directory {
                    let result = read_file_content(&abs_path);
                    match result {
                        Ok(info) => self.show_info(info),
                        Err(err) => self.show_error(err.to_string()),
                    }
                }
            }
        }
        self.populate_current_child_nodes();
    }

    fn open_action_menu_step2(&mut self, title: String, buffer: String) {
        self.window_focus = WindowFocus::ActionMenuStep2;
        self.action_menu_title = title;
        self.action_menu_buffer = buffer;
        self.action_menu_cursor_x = self.action_menu_buffer.chars().count();
    }

    pub fn execute_dialog_action_step2(&mut self, tui: &mut Tui) {
        let abs_path: String = match self.get_selected_abs_path() {
            Some(abs_path) => abs_path,
            None => return,
        };
        let tree_node: TreeNode = match self.get_selected_tree_node() {
            Some(tree_node) => tree_node,
            None => return,
        };
        if self.action_menu_buffer.is_empty() {
            self.show_error("No value given".to_string());
            return;
        }
        let current_dir_path: String = self.get_current_dir_abs_path();

        match self.action_menu_operation {
            Some(Operation::Rename) => {
                let result = rename_file(&abs_path, &self.action_menu_buffer);
                match result {
                    Err(err) => self.show_error(err.to_string()),
                    _ => {}
                }
            }
            Some(Operation::Delete) => {
                if &self.action_menu_buffer != "yes" {
                    self.show_error("Operation aborted".to_string());
                    return;
                }
                let result = delete_tree_node(&tree_node, &abs_path);
                match result {
                    Err(err) => self.show_error(err.to_string()),
                    _ => {}
                }
            }
            Some(Operation::CreateFile) => {
                let full_path = format!("{}/{}", current_dir_path, &self.action_menu_buffer);
                let result = create_file(&full_path);
                match result {
                    Err(err) => self.show_error(err.to_string()),
                    _ => {}
                }
            }
            Some(Operation::CreateDir) => {
                let full_path = format!("{}/{}", current_dir_path, &self.action_menu_buffer);
                let result = create_directory(&full_path);
                match result {
                    Err(err) => self.show_error(err.to_string()),
                    _ => {}
                }
            }
            Some(Operation::CustomCommand) => {
                let current_dir_path = current_dir_path.clone();
                let action_menu_buffer = self.action_menu_buffer.clone();
                self.show_info(format!("Running command...\n{}", &self.action_menu_buffer));
                let result_tx = self.background_event_channel.tx.clone();
                std::thread::spawn(move || {
                    let result = run_custom_command(current_dir_path, &action_menu_buffer);
                    match result {
                        Ok(output) => result_tx.send(BackgroundEvent::InfoMessage(output)),
                        Err(err) => result_tx.send(BackgroundEvent::ErrorMessage(err.to_string())),
                    }
                });
            }
            Some(Operation::CustomInteractiveCommand) => {
                let result =
                    run_custom_interactive_command(current_dir_path, &self.action_menu_buffer, tui);
                match result {
                    Ok(output) => self.show_info(output),
                    Err(err) => self.show_error(err.to_string()),
                }
            }
            _ => {}
        }
        self.window_focus = WindowFocus::Tree;
        self.populate_current_child_nodes();
    }

    pub fn rename_selected_node(&mut self) {
        let abs_path: String = match self.get_selected_abs_path() {
            Some(abs_path) => abs_path,
            None => return,
        };
        let filename = abs_path.split('/').last().unwrap().to_string();
        self.action_menu_operation = Some(Operation::Rename);
        self.open_action_menu_step2(format!("New name for {}", filename), filename);
    }

    pub fn delete_selected_node_confirm(&mut self) {
        let abs_path: String = match self.get_selected_abs_path() {
            Some(abs_path) => abs_path,
            None => return,
        };
        let filename = abs_path.split('/').last().unwrap().to_string();
        self.action_menu_operation = Some(Operation::Delete);
        self.open_action_menu_step2(
            format!(
                "Are you sure you want to permanently delete \"{}\"?",
                filename
            ),
            "yes".to_string(),
        );
    }

    pub fn action_menu_input_append(&mut self, c: char) {
        let chars: Chars<'_> = self.action_menu_buffer.chars();
        let cx = self.action_menu_cursor_x;
        let before: String = chars.clone().take(cx).collect::<String>();
        let after: String = chars.skip(cx).collect::<String>();
        self.action_menu_buffer = before + &c.to_string() + &after;
        self.action_menu_input_right();
    }

    pub fn action_menu_input_clear_backwards(&mut self) {
        let chars: Chars<'_> = self.action_menu_buffer.chars();
        let cx = self.action_menu_cursor_x;
        let after: String = chars.skip(cx).collect::<String>();
        self.action_menu_buffer = after;
        self.action_menu_cursor_x = 0;
    }

    pub fn action_menu_input_clear_forward(&mut self) {
        let chars: Chars<'_> = self.action_menu_buffer.chars();
        let cx = self.action_menu_cursor_x;
        let before: String = chars.clone().take(cx).collect::<String>();
        self.action_menu_buffer = before;
    }

    pub fn action_menu_input_backspace(&mut self) {
        let chars: Chars<'_> = self.action_menu_buffer.chars();
        let cx = self.action_menu_cursor_x;
        let mut before: String = chars.clone().take(cx).collect::<String>();
        let after: String = chars.skip(cx).collect::<String>();
        if !before.is_empty() {
            before.pop();
            self.action_menu_buffer = before + &after;
            self.action_menu_input_left();
        }
    }

    pub fn action_menu_input_delete(&mut self) {
        let chars: Chars<'_> = self.action_menu_buffer.chars();
        let cx = self.action_menu_cursor_x;
        let before: String = chars.clone().take(cx).collect::<String>();
        let mut after: String = chars.skip(cx).collect::<String>();
        if !after.is_empty() {
            after.remove(0);
            self.action_menu_buffer = before + &after;
        }
    }

    pub fn action_menu_input_backspace_word(&mut self) {
        let chars: Chars<'_> = self.action_menu_buffer.chars();
        let cx = self.action_menu_cursor_x;
        let before: String = chars.clone().take(cx).collect::<String>();
        let after: String = chars.skip(cx).collect::<String>();
        let mut before_words = before.split(' ').collect::<Vec<&str>>();
        if !before_words.is_empty() {
            if before_words.last().unwrap().is_empty() {
                before_words.pop();
            } else {
                *before_words.last_mut().unwrap() = "";
            }
            let before: String = before_words.join(" ");
            self.action_menu_cursor_x = before.chars().count();
            self.action_menu_buffer = before + &after;
        }
    }

    pub fn action_menu_input_delete_word(&mut self) {
        let chars: Chars<'_> = self.action_menu_buffer.chars();
        let cx = self.action_menu_cursor_x;
        let before: String = chars.clone().take(cx).collect::<String>();
        let after: String = chars.skip(cx).collect::<String>();
        let mut after_words = after.split(' ').collect::<Vec<&str>>();
        if !after_words.is_empty() {
            if after_words.first().unwrap().is_empty() {
                after_words.remove(0);
            } else {
                *after_words.first_mut().unwrap() = "";
            }
            let after: String = after_words.join(" ");
            self.action_menu_buffer = before + &after;
        }
    }

    pub fn action_menu_input_left_word(&mut self) {
        let chars: Chars<'_> = self.action_menu_buffer.chars();
        let cx = self.action_menu_cursor_x;
        let before: String = chars.clone().take(cx).collect::<String>();
        let mut before_words = before.split(' ').collect::<Vec<&str>>();
        if !before_words.is_empty() {
            if before_words.last().unwrap().is_empty() {
                before_words.pop();
            } else {
                *before_words.last_mut().unwrap() = "";
            }
            let before: String = before_words.join(" ");
            self.action_menu_cursor_x = before.chars().count();
        }
    }

    pub fn action_menu_input_right_word(&mut self) {
        let chars: Chars<'_> = self.action_menu_buffer.chars();
        let cx = self.action_menu_cursor_x;
        let after: String = chars.skip(cx).collect::<String>();
        let after_words = after.split(' ').collect::<Vec<&str>>();
        if !after_words.is_empty() {
            if after_words.first().unwrap().is_empty() {
                self.action_menu_cursor_x += 1;
            } else {
                self.action_menu_cursor_x += after_words.first().unwrap().chars().count();
            }
        }
    }

    pub fn action_menu_input_left(&mut self) {
        if self.action_menu_cursor_x > 0 {
            self.action_menu_cursor_x -= 1;
        }
    }

    pub fn action_menu_input_right(&mut self) {
        self.action_menu_cursor_x += 1;
        let length = self.action_menu_buffer.chars().count();
        if self.action_menu_cursor_x > length {
            self.action_menu_cursor_x = length;
        }
    }

    pub fn action_menu_input_home(&mut self) {
        self.action_menu_cursor_x = 0;
    }

    pub fn action_menu_input_end(&mut self) {
        self.action_menu_cursor_x = self.action_menu_buffer.chars().count();
    }
}