use std::borrow::Borrow;
use std::path;
use anyhow::{Context, Result};
use indicatif::InMemoryTerm;
use crate::app::{Direction, Focus, Status, Tab};
use crate::common::{
content_to_clipboard, filename_to_clipboard, filepath_to_clipboard, get_clipboard,
open_in_current_neovim, set_clipboard, set_current_dir, tilde, CONFIG_PATH,
};
use crate::config::{Bindings, START_FOLDER};
use crate::io::{read_log, External};
use crate::log_info;
use crate::log_line;
use crate::modes::{
help_string, lsblk_and_udisksctl_installed, Content, ContentWindow,
Direction as FuzzyDirection, Display, DoneCopyMove, FuzzyKind, Go, InputCompleted, InputSimple,
LeaveMenu, MarkAction, Menu, Navigate, NeedConfirmation, Preview, PreviewBuilder, ReEnterMenu,
Search, Selectable, To,
};
pub struct EventAction {}
impl EventAction {
pub fn quit(status: &mut Status) -> Result<()> {
if status.focus.is_file() {
status.internal_settings.quit();
} else {
status.reset_menu_mode()?;
}
Ok(())
}
pub fn refresh_view(status: &mut Status) -> Result<()> {
status.refresh_view()
}
pub fn refresh_if_needed(status: &mut Status) -> Result<()> {
status.menu.flagged.remove_non_existant();
status.tabs[0].refresh_if_needed()?;
status.tabs[1].refresh_if_needed()
}
pub fn paste(status: &mut Status, pasted: String) -> Result<()> {
log_info!("pasted: ###'{pasted}'###");
let pasted = pasted.trim();
let display_mode = status.current_tab().display_mode;
if status.focus.is_file() && (display_mode.is_tree() || display_mode.is_directory()) {
status.paste_pathes(pasted)
} else {
status.paste_input(pasted)
}
}
pub fn resize(status: &mut Status, width: u16, height: u16) -> Result<()> {
status.resize(width, height)
}
pub fn reset_mode(status: &mut Status) -> Result<()> {
if status.focus.is_file() && status.current_tab().display_mode.is_preview() {
status.leave_preview()?;
}
if matches!(status.current_tab().menu_mode, Menu::Nothing) {
status.current_tab_mut().reset_visual();
return Ok(());
};
status.leave_menu_mode()?;
status.menu.input.reset();
status.menu.completion.reset();
Ok(())
}
pub fn toggle_display_full(status: &mut Status) -> Result<()> {
status.session.toggle_metadata();
Ok(())
}
pub fn toggle_dualpane(status: &mut Status) -> Result<()> {
status.clear_preview_right();
status.session.toggle_dual();
status.select_left();
Ok(())
}
pub fn toggle_preview_second(status: &mut Status) -> Result<()> {
if !status.session.dual() {
Self::toggle_dualpane(status)?;
status.session.set_preview();
} else {
status.session.toggle_preview();
}
if status.session.preview() {
status.update_second_pane_for_preview()
} else {
status.set_menu_mode(1, Menu::Nothing)?;
status.tabs[1].display_mode = Display::Directory;
status.tabs[1].refresh_view()
}
}
pub fn tree(status: &mut Status) -> Result<()> {
if !status.focus.is_file() {
return Ok(());
}
status.current_tab_mut().toggle_tree_mode()?;
status.refresh_view()
}
pub fn tree_depth_decr(status: &mut Status) -> Result<()> {
if !status.focus.is_file() && status.current_tab().display_mode.is_tree() {
return Ok(());
}
let current_depth = status.current_tab().settings.tree_max_depth;
if current_depth > 1 {
status.current_tab_mut().settings.tree_max_depth -= 1;
let new_depth = status.current_tab().settings.tree_max_depth;
log_info!("Decrease tree depth current: {new_depth}");
log_line!("Decrease tree depth current: {new_depth}");
}
status.current_tab_mut().toggle_tree_mode()?;
status.current_tab_mut().toggle_tree_mode()?;
Ok(())
}
pub fn tree_depth_incr(status: &mut Status) -> Result<()> {
if !status.focus.is_file() && status.current_tab().display_mode.is_tree() {
return Ok(());
}
status.current_tab_mut().settings.tree_max_depth += 1;
let new_depth = status.current_tab().settings.tree_max_depth;
log_info!("Increase tree depth current: {new_depth}");
log_line!("Increase tree depth current: {new_depth}");
status.current_tab_mut().toggle_tree_mode()?;
status.current_tab_mut().toggle_tree_mode()?;
Ok(())
}
pub fn tree_fold(status: &mut Status) -> Result<()> {
if !status.focus.is_file() {
return Ok(());
}
let tab = status.current_tab_mut();
tab.tree.toggle_fold();
Ok(())
}
pub fn tree_unfold_all(status: &mut Status) -> Result<()> {
if !status.focus.is_file() {
return Ok(());
}
let tab = status.current_tab_mut();
tab.tree.unfold_all();
Ok(())
}
pub fn tree_fold_all(status: &mut Status) -> Result<()> {
if !status.focus.is_file() {
return Ok(());
}
let tab = status.current_tab_mut();
tab.tree.fold_all();
Ok(())
}
pub fn display_flagged(status: &mut Status) -> Result<()> {
if !status.focus.is_file() {
return Ok(());
}
let menu_mode = &status.current_tab().menu_mode;
if matches!(menu_mode, Menu::Navigate(Navigate::Flagged)) {
status.leave_menu_mode()?;
} else if matches!(menu_mode, Menu::Nothing) {
status.set_menu_mode(status.index, Menu::Navigate(Navigate::Flagged))?;
}
Ok(())
}
pub fn preview(status: &mut Status) -> Result<()> {
if !status.focus.is_file() {
return Ok(());
}
status.current_tab_mut().make_preview()
}
pub fn toggle_hidden(status: &mut Status) -> Result<()> {
if !status.focus.is_file() || status.current_tab().display_mode.is_preview() {
return Ok(());
}
status.current_tab_mut().toggle_hidden()
}
pub fn clear_flags(status: &mut Status) -> Result<()> {
if status.focus.is_file() {
status.menu.flagged.clear();
}
Ok(())
}
pub fn flag_all(status: &mut Status) -> Result<()> {
if status.focus.is_file() {
status.flag_all();
}
Ok(())
}
pub fn flagged_to_clipboard(status: &mut Status) -> Result<()> {
set_clipboard(status.menu.flagged.content_to_string());
Ok(())
}
pub fn flagged_from_clipboard(status: &mut Status) -> Result<()> {
let Some(files) = get_clipboard() else {
return Ok(());
};
if files.is_empty() {
return Ok(());
}
log_info!("clipboard read: {files}");
status.menu.flagged.replace_by_string(files);
Ok(())
}
pub fn reverse_flags(status: &mut Status) -> Result<()> {
if status.focus.is_file() {
status.reverse_flags();
}
Ok(())
}
pub fn toggle_flag(status: &mut Status) -> Result<()> {
if status.focus.is_file() {
status.toggle_flag_for_selected();
}
Ok(())
}
pub fn toggle_flag_children(status: &mut Status) -> Result<()> {
if status.focus.is_file() {
status.toggle_flag_for_children();
}
Ok(())
}
pub fn reenter_menu_from_picker(status: &mut Status, menu: Menu) -> Result<()> {
menu.reenter(status)?;
if !menu.is_input() {
return Ok(());
}
let Some(picked) = &status.menu.picker.selected() else {
return Ok(());
};
status.menu.input.replace(picked);
let Menu::InputCompleted(input_completed) = menu else {
return Ok(());
};
status.complete(input_completed)
}
pub fn rename(status: &mut Status) -> Result<()> {
if matches!(
status.current_tab().menu_mode,
Menu::InputSimple(InputSimple::Rename)
) {
status.reset_menu_mode()?;
return Ok(());
};
let selected_path = status
.current_tab()
.selected_path()
.context("No selected file")?;
if selected_path == status.current_tab().directory.path {
return Ok(());
}
if let Some(parent) = status.current_tab().directory.path.parent() {
if selected_path.as_ref() == parent {
return Ok(());
}
}
let old_name = &selected_path.to_string_lossy();
status.set_menu_mode(status.index, Menu::InputSimple(InputSimple::Rename))?;
status.menu.input.replace(old_name);
Ok(())
}
pub fn copy_paste(status: &mut Status) -> Result<()> {
if matches!(
status.current_tab().menu_mode,
Menu::NeedConfirmation(NeedConfirmation::Copy)
) {
status.reset_menu_mode()?;
} else {
Self::set_copy_paste(status, NeedConfirmation::Copy)?;
}
Ok(())
}
pub fn cut_paste(status: &mut Status) -> Result<()> {
if matches!(
status.current_tab().menu_mode,
Menu::NeedConfirmation(NeedConfirmation::Move)
) {
status.reset_menu_mode()?;
} else {
Self::set_copy_paste(status, NeedConfirmation::Move)?;
}
Ok(())
}
fn set_copy_paste(status: &mut Status, copy_or_move: NeedConfirmation) -> Result<()> {
if status.menu.flagged.is_empty() {
return Ok(());
}
status.set_menu_mode(status.index, Menu::NeedConfirmation(copy_or_move))
}
pub fn symlink(status: &mut Status) -> Result<()> {
if !status.focus.is_file() {
return Ok(());
}
for original_file in status.menu.flagged.content.iter() {
let filename = original_file
.as_path()
.file_name()
.context("event symlink: File not found")?;
let link = status.current_tab().directory_of_selected()?.join(filename);
std::os::unix::fs::symlink(original_file, &link)?;
log_line!(
"Symlink {link} links to {original_file}",
original_file = original_file.display(),
link = link.display()
);
}
status.clear_flags_and_reset_view()
}
pub fn delete_file(status: &mut Status) -> Result<()> {
if status.menu.flagged.is_empty() {
Self::toggle_flag(status)?;
}
status.set_menu_mode(
status.index,
Menu::NeedConfirmation(NeedConfirmation::Delete),
)
}
pub fn chmod(status: &mut Status) -> Result<()> {
if matches!(
status.current_tab().menu_mode,
Menu::InputSimple(InputSimple::Chmod)
) {
status.reset_menu_mode()?;
} else {
status.set_mode_chmod()?;
}
Ok(())
}
fn new_node(status: &mut Status, input_kind: InputSimple) -> Result<()> {
if !matches!(input_kind, InputSimple::Newdir | InputSimple::Newfile) {
return Ok(());
}
if matches!(
status.current_tab().menu_mode,
Menu::InputSimple(InputSimple::Newdir | InputSimple::Newfile)
) {
status.reset_menu_mode()?;
return Ok(());
}
if matches!(
status.current_tab().display_mode,
Display::Directory | Display::Tree
) {
status.set_menu_mode(status.index, Menu::InputSimple(input_kind))?;
}
Ok(())
}
pub fn new_dir(status: &mut Status) -> Result<()> {
Self::new_node(status, InputSimple::Newdir)
}
pub fn new_file(status: &mut Status) -> Result<()> {
Self::new_node(status, InputSimple::Newfile)
}
fn enter_file(status: &mut Status) -> Result<()> {
match status.current_tab_mut().display_mode {
Display::Directory => Self::normal_enter_file(status),
Display::Tree => Self::tree_enter_file(status),
Display::Fuzzy => status.fuzzy_select(),
Display::Preview => status.enter_from_preview(),
}
}
fn normal_enter_file(status: &mut Status) -> Result<()> {
let tab = status.current_tab_mut();
if tab.display_mode.is_tree() {
return EventAction::open_file(status);
};
if tab.directory.is_empty() {
return Ok(());
}
if tab.directory.is_selected_dir()? {
tab.go_to_selected_dir()?;
status.thumbnail_directory_video();
Ok(())
} else {
EventAction::open_file(status)
}
}
fn tree_enter_file(status: &mut Status) -> Result<()> {
let path = status
.current_tab()
.selected_path()
.context("No selected file")?;
if path.is_dir() {
status.current_tab_mut().tree_enter_dir(path)
} else {
EventAction::open_file(status)
}
}
pub fn open_file(status: &mut Status) -> Result<()> {
if !status.focus.is_file() {
return Ok(());
}
if status.menu.flagged.is_empty() {
status.open_selected_file()
} else {
status.open_flagged_files()
}
}
pub fn open_all(status: &mut Status) -> Result<()> {
status.open_flagged_files()
}
pub fn exec(status: &mut Status) -> Result<()> {
if matches!(
status.current_tab().menu_mode,
Menu::InputCompleted(InputCompleted::Exec)
) {
status.reset_menu_mode()?;
return Ok(());
}
if status.menu.flagged.is_empty() {
status.menu.flagged.push(
status
.current_tab()
.selected_path()
.context("No selected file")?
.to_path_buf(),
);
}
status.set_menu_mode(status.index, Menu::InputCompleted(InputCompleted::Exec))
}
pub fn sort(status: &mut Status) -> Result<()> {
if matches!(
status.current_tab().menu_mode,
Menu::InputSimple(InputSimple::Sort)
) {
status.reset_menu_mode()?;
}
status.set_height_for_menu_mode(status.index, Menu::Nothing)?;
status.tabs[status.index].menu_mode = Menu::Nothing;
let len = status.menu.len(Menu::Nothing);
let height = status.second_window_height()?;
status.menu.window = ContentWindow::new(len, height);
status.tabs[status.index].menu_mode = Menu::InputSimple(InputSimple::Sort);
status.set_focus_from_mode();
Ok(())
}
pub fn filter(status: &mut Status) -> Result<()> {
if matches!(
status.current_tab().menu_mode,
Menu::InputSimple(InputSimple::Filter)
) {
status.reset_menu_mode()?;
} else if matches!(
status.current_tab().display_mode,
Display::Tree | Display::Directory
) {
status.set_menu_mode(status.index, Menu::InputSimple(InputSimple::Filter))?;
}
Ok(())
}
pub fn bulk(status: &mut Status) -> Result<()> {
if !status.focus.is_file() {
return Ok(());
}
status.bulk_ask_filenames()?;
Ok(())
}
pub fn search(status: &mut Status) -> Result<()> {
if matches!(
status.current_tab().menu_mode,
Menu::InputCompleted(InputCompleted::Search)
) {
status.reset_menu_mode()?;
}
let tab = status.current_tab_mut();
tab.search = Search::empty();
status.set_menu_mode(status.index, Menu::InputCompleted(InputCompleted::Search))
}
pub fn regex_match(status: &mut Status) -> Result<()> {
if matches!(
status.current_tab().menu_mode,
Menu::InputSimple(InputSimple::RegexMatch)
) {
status.reset_menu_mode()?;
}
if matches!(
status.current_tab().display_mode,
Display::Tree | Display::Directory
) {
status.set_menu_mode(status.index, Menu::InputSimple(InputSimple::RegexMatch))
} else {
Ok(())
}
}
pub fn help(status: &mut Status, binds: &Bindings) -> Result<()> {
if !status.focus.is_file() {
return Ok(());
}
let help = help_string(binds, &status.internal_settings.opener);
status.current_tab_mut().set_display_mode(Display::Preview);
status.current_tab_mut().preview = PreviewBuilder::help(&help);
let len = status.current_tab().preview.len();
status.current_tab_mut().window.reset(len);
Ok(())
}
pub fn log(status: &mut Status) -> Result<()> {
if !status.focus.is_file() {
return Ok(());
}
let Ok(log) = read_log() else {
return Ok(());
};
let tab = status.current_tab_mut();
tab.set_display_mode(Display::Preview);
tab.preview = PreviewBuilder::log(log);
tab.window.reset(tab.preview.len());
tab.preview_go_bottom();
Ok(())
}
pub fn cd(status: &mut Status) -> Result<()> {
if matches!(
status.current_tab().menu_mode,
Menu::InputCompleted(InputCompleted::Cd)
) {
status.reset_menu_mode()?;
} else {
status.set_menu_mode(status.index, Menu::InputCompleted(InputCompleted::Cd))?;
status.tabs[status.index].save_origin_path();
status.menu.completion.reset();
}
Ok(())
}
pub fn shell(status: &mut Status) -> Result<()> {
if !status.focus.is_file() {
return Ok(());
}
set_current_dir(status.current_tab().current_directory_path())?;
status.internal_settings.disable_display();
External::open_shell_in_window()?;
status.internal_settings.enable_display();
Ok(())
}
pub fn shell_command(status: &mut Status) -> Result<()> {
if matches!(
status.current_tab().menu_mode,
Menu::InputSimple(InputSimple::ShellCommand)
) {
status.reset_menu_mode()?;
}
status.set_menu_mode(status.index, Menu::InputSimple(InputSimple::ShellCommand))
}
pub fn tui_menu(status: &mut Status) -> Result<()> {
if matches!(
status.current_tab().menu_mode,
Menu::Navigate(Navigate::TuiApplication)
) {
status.reset_menu_mode()?;
} else {
if status.menu.tui_applications.is_not_set() {
status.menu.tui_applications.setup();
}
status.set_menu_mode(status.index, Menu::Navigate(Navigate::TuiApplication))?;
}
Ok(())
}
pub fn cli_menu(status: &mut Status) -> Result<()> {
if matches!(
status.current_tab().menu_mode,
Menu::Navigate(Navigate::CliApplication)
) {
status.reset_menu_mode()?;
} else {
if status.menu.cli_applications.is_empty() {
status.menu.cli_applications.setup();
}
status.set_menu_mode(status.index, Menu::Navigate(Navigate::CliApplication))?;
}
Ok(())
}
pub fn history(status: &mut Status) -> Result<()> {
if matches!(
status.current_tab().menu_mode,
Menu::Navigate(Navigate::History)
) {
status.reset_menu_mode()?;
} else if matches!(
status.current_tab().display_mode,
Display::Directory | Display::Tree
) {
status.set_menu_mode(status.index, Menu::Navigate(Navigate::History))?;
}
Ok(())
}
pub fn marks_new(status: &mut Status) -> Result<()> {
if matches!(
status.current_tab().menu_mode,
Menu::Navigate(Navigate::Marks(MarkAction::New))
) {
status.reset_menu_mode()?;
} else {
if status.menu.marks.is_empty() {
status.menu.marks.setup();
}
status.set_menu_mode(
status.index,
Menu::Navigate(Navigate::Marks(MarkAction::New)),
)?;
}
Ok(())
}
pub fn marks_jump(status: &mut Status) -> Result<()> {
if matches!(
status.current_tab().menu_mode,
Menu::Navigate(Navigate::Marks(MarkAction::Jump))
) {
status.reset_menu_mode()?;
} else {
if status.menu.marks.is_empty() {
status.menu.marks.setup();
}
if status.menu.marks.is_empty() {
return Ok(());
}
status.set_menu_mode(
status.index,
Menu::Navigate(Navigate::Marks(MarkAction::Jump)),
)?;
}
Ok(())
}
pub fn temp_marks_jump(status: &mut Status) -> Result<()> {
if matches!(
status.current_tab().menu_mode,
Menu::Navigate(Navigate::TempMarks(MarkAction::Jump))
) {
status.reset_menu_mode()?;
} else {
status.set_menu_mode(
status.index,
Menu::Navigate(Navigate::TempMarks(MarkAction::Jump)),
)?;
}
Ok(())
}
pub fn temp_marks_new(status: &mut Status) -> Result<()> {
if matches!(
status.current_tab().menu_mode,
Menu::Navigate(Navigate::TempMarks(MarkAction::New))
) {
status.reset_menu_mode()?;
} else {
status.set_menu_mode(
status.index,
Menu::Navigate(Navigate::TempMarks(MarkAction::New)),
)?;
}
Ok(())
}
pub fn shortcut(status: &mut Status) -> Result<()> {
if matches!(
status.current_tab().menu_mode,
Menu::Navigate(Navigate::Shortcut)
) {
status.reset_menu_mode()?;
} else {
status.refresh_shortcuts();
set_current_dir(status.current_tab().directory_of_selected()?)?;
status.set_menu_mode(status.index, Menu::Navigate(Navigate::Shortcut))?;
}
Ok(())
}
pub fn nvim_filepicker(status: &mut Status) -> Result<()> {
if !status.focus.is_file() {
return Ok(());
}
status.update_nvim_listen_address();
if status.internal_settings.nvim_server.is_empty() {
return Ok(());
};
let nvim_server = &status.internal_settings.nvim_server;
if status.menu.flagged.is_empty() {
let Some(path) = status.current_tab().selected_path() else {
return Ok(());
};
open_in_current_neovim(&path, nvim_server);
} else {
for file_path in status.menu.flagged.content.iter() {
open_in_current_neovim(file_path, nvim_server)
}
}
Ok(())
}
pub fn set_nvim_server(status: &mut Status) -> Result<()> {
if matches!(
status.current_tab().menu_mode,
Menu::InputSimple(InputSimple::SetNvimAddr)
) {
status.reset_menu_mode()?;
return Ok(());
};
status.set_menu_mode(status.index, Menu::InputSimple(InputSimple::SetNvimAddr))
}
pub fn back(status: &mut Status) -> Result<()> {
if !status.focus.is_file() {
return Ok(());
}
status.current_tab_mut().back()?;
status.update_second_pane_for_preview()
}
pub fn home(status: &mut Status) -> Result<()> {
if !status.focus.is_file() {
return Ok(());
}
let home_cow = tilde("~");
let home: &str = home_cow.borrow();
let home_path = path::Path::new(home);
status.current_tab_mut().cd(home_path)?;
status.update_second_pane_for_preview()
}
pub fn go_root(status: &mut Status) -> Result<()> {
if !status.focus.is_file() {
return Ok(());
}
let root_path = std::path::PathBuf::from("/");
status.current_tab_mut().cd(&root_path)?;
status.update_second_pane_for_preview()
}
pub fn go_start(status: &mut Status) -> Result<()> {
if !status.focus.is_file() {
return Ok(());
}
status
.current_tab_mut()
.cd(START_FOLDER.get().context("Start folder should be set")?)?;
status.update_second_pane_for_preview()
}
pub fn search_next(status: &mut Status) -> Result<()> {
if !status.focus.is_file() {
return Ok(());
}
match status.current_tab().display_mode {
Display::Tree => status.tabs[status.index]
.search
.tree(&mut status.tabs[status.index].tree),
Display::Directory => status.current_tab_mut().directory_search_next(),
Display::Preview | Display::Fuzzy => {
return Ok(());
}
}
status.refresh_status()?;
status.update_second_pane_for_preview()?;
Ok(())
}
pub fn move_up(status: &mut Status) -> Result<()> {
if status.focus.is_file() {
Self::move_display_up(status)?;
} else {
let tab = status.current_tab_mut();
match tab.menu_mode {
Menu::Nothing => Self::move_display_up(status)?,
Menu::Navigate(Navigate::History) => tab.history.prev(),
Menu::Navigate(navigate) => status.menu.prev(navigate),
Menu::InputCompleted(input_completed) => {
status.menu.completion_prev(input_completed);
if matches!(input_completed, InputCompleted::Search) {
status.follow_search()?;
}
}
Menu::NeedConfirmation(need_confirmation)
if need_confirmation.use_flagged_files() =>
{
status.menu.prev(Navigate::Flagged)
}
Menu::NeedConfirmation(NeedConfirmation::EmptyTrash) => {
status.menu.prev(Navigate::Trash)
}
Menu::NeedConfirmation(NeedConfirmation::BulkAction) => {
status.menu.bulk.prev();
status.menu.window.scroll_to(status.menu.bulk.index())
}
_ => (),
};
}
status.update_second_pane_for_preview()
}
pub fn next_thing(status: &mut Status) -> Result<()> {
if status.current_tab().display_mode.is_tree() && status.focus.is_file() {
status.current_tab_mut().tree_next_sibling();
} else {
status.input_history_prev()?;
}
Ok(())
}
pub fn previous_thing(status: &mut Status) -> Result<()> {
if status.current_tab().display_mode.is_tree() && status.focus.is_file() {
status.current_tab_mut().tree_prev_sibling();
} else {
status.input_history_next()?;
}
Ok(())
}
pub fn next_word(status: &mut Status) -> Result<()> {
if status.current_tab().menu_mode.is_input() && !status.focus.is_file() {
status.menu.input.next_word();
}
Ok(())
}
pub fn previous_word(status: &mut Status) -> Result<()> {
if status.current_tab().menu_mode.is_input() && !status.focus.is_file() {
status.menu.input.previous_word();
}
Ok(())
}
fn move_display_up(status: &mut Status) -> Result<()> {
let tab = status.current_tab_mut();
match tab.display_mode {
Display::Directory => {
tab.normal_up_one_row();
status.toggle_flag_visual();
}
Display::Preview if matches!(&tab.preview, Preview::Tree(_)) => {
if let Preview::Tree(tree) = &mut tab.preview {
tree.go(To::Prev);
tab.window.scroll_up_one(tree.displayable().index());
}
}
Display::Preview => tab.preview_page_up(),
Display::Tree => {
tab.tree_select_prev();
status.toggle_flag_visual();
}
Display::Fuzzy => status.fuzzy_navigate(FuzzyDirection::Up)?,
}
Ok(())
}
fn move_display_down(status: &mut Status) -> Result<()> {
let tab = status.current_tab_mut();
match tab.display_mode {
Display::Directory => {
tab.normal_down_one_row();
status.toggle_flag_visual();
}
Display::Preview if matches!(&tab.preview, Preview::Tree(_)) => {
if let Preview::Tree(tree) = &mut tab.preview {
tree.go(To::Next);
tab.window.scroll_down_one(tree.displayable().index());
}
}
Display::Preview => tab.preview_page_down(),
Display::Tree => {
tab.tree_select_next();
status.toggle_flag_visual()
}
Display::Fuzzy => status.fuzzy_navigate(FuzzyDirection::Down)?,
}
Ok(())
}
pub fn move_down(status: &mut Status) -> Result<()> {
if status.focus.is_file() {
Self::move_display_down(status)?
} else {
match status.current_tab_mut().menu_mode {
Menu::Nothing => Self::move_display_down(status)?,
Menu::Navigate(Navigate::History) => status.current_tab_mut().history.next(),
Menu::Navigate(navigate) => status.menu.next(navigate),
Menu::InputCompleted(input_completed) => {
status.menu.completion_next(input_completed);
if matches!(input_completed, InputCompleted::Search) {
status.follow_search()?;
}
}
Menu::NeedConfirmation(need_confirmation)
if need_confirmation.use_flagged_files() =>
{
status.menu.next(Navigate::Flagged)
}
Menu::NeedConfirmation(NeedConfirmation::EmptyTrash) => {
status.menu.next(Navigate::Trash)
}
Menu::NeedConfirmation(NeedConfirmation::BulkAction) => {
status.menu.bulk.next();
status.menu.window.scroll_to(status.menu.bulk.index())
}
_ => (),
};
}
status.update_second_pane_for_preview()
}
pub fn move_left(status: &mut Status) -> Result<()> {
if status.focus.is_file() {
Self::file_move_left(status.current_tab_mut())?;
} else {
let tab = status.current_tab_mut();
match tab.menu_mode {
Menu::InputSimple(_) | Menu::InputCompleted(_) => {
status.menu.input.cursor_left();
}
Menu::Nothing => Self::file_move_left(tab)?,
Menu::Navigate(Navigate::Cloud) => status.cloud_move_to_parent()?,
_ => (),
}
}
status.update_second_pane_for_preview()
}
fn file_move_left(tab: &mut Tab) -> Result<()> {
match tab.display_mode {
Display::Directory => tab.move_to_parent()?,
Display::Tree => tab.tree_select_parent()?,
_ => (),
};
Ok(())
}
pub fn move_right(status: &mut Status) -> Result<()> {
if status.focus.is_file() {
Self::enter_file(status)
} else {
let tab: &mut Tab = status.current_tab_mut();
match tab.menu_mode {
Menu::InputSimple(_) | Menu::InputCompleted(_) => {
status.menu.input.cursor_right();
Ok(())
}
Menu::Navigate(Navigate::Cloud) => status.cloud_enter_file_or_dir(),
Menu::Nothing => Self::enter_file(status),
_ => Ok(()),
}
}
}
pub fn focus_follow_mouse(status: &mut Status, row: u16, col: u16) -> Result<()> {
status.set_focus_from_pos(row, col)?;
Ok(())
}
fn click(status: &mut Status, binds: &Bindings, row: u16, col: u16) -> Result<()> {
status.click(binds, row, col)
}
pub fn left_click(status: &mut Status, binds: &Bindings, row: u16, col: u16) -> Result<()> {
Self::click(status, binds, row, col)
}
pub fn right_click(status: &mut Status, binds: &Bindings, row: u16, col: u16) -> Result<()> {
Self::click(status, binds, row, col)?;
Self::context(status)
}
pub fn wheel_up(status: &mut Status, row: u16, col: u16) -> Result<()> {
status.set_focus_from_pos(row, col)?;
Self::move_up(status)
}
pub fn wheel_down(status: &mut Status, row: u16, col: u16) -> Result<()> {
status.set_focus_from_pos(row, col)?;
Self::move_down(status)
}
pub fn middle_click(status: &mut Status, binds: &Bindings, row: u16, col: u16) -> Result<()> {
if Self::click(status, binds, row, col).is_ok() {
if status.focus.is_file() {
Self::enter_file(status)?;
} else {
LeaveMenu::leave_menu(status, binds)?;
}
};
Ok(())
}
pub fn backspace(status: &mut Status) -> Result<()> {
if status.focus.is_file() {
return Ok(());
}
match status.current_tab().menu_mode {
Menu::Navigate(Navigate::Marks(_)) => {
status.menu.marks.remove_selected()?;
}
Menu::Navigate(Navigate::TempMarks(_)) => {
status.menu.temp_marks.erase_current_mark();
}
Menu::InputSimple(_) => {
status.menu.input.delete_char_left();
}
Menu::InputCompleted(input_completed) => {
status.menu.input.delete_char_left();
status.complete(input_completed)?;
if matches!(input_completed, InputCompleted::Search) {
status.follow_search()?;
}
}
_ => (),
}
Ok(())
}
pub fn delete(status: &mut Status) -> Result<()> {
if status.focus.is_file() {
Self::delete_file(status)
} else {
match status.current_tab_mut().menu_mode {
Menu::InputSimple(_) => {
status.menu.input.delete_chars_right();
Ok(())
}
Menu::InputCompleted(input_completed) => {
status.menu.input.delete_chars_right();
status.complete(input_completed)?;
if matches!(input_completed, InputCompleted::Search) {
status.follow_search()?;
}
Ok(())
}
_ => Ok(()),
}
}
}
pub fn delete_line(status: &mut Status) -> Result<()> {
if status.focus.is_file() {
status.sync_tabs(Direction::RightToLeft)?;
}
match status.current_tab_mut().menu_mode {
Menu::InputSimple(_) => {
status.menu.input.delete_line();
}
Menu::InputCompleted(_) => {
status.menu.input.delete_line();
status.menu.completion_reset();
}
_ => (),
}
Ok(())
}
pub fn delete_left(status: &mut Status) -> Result<()> {
if status.focus.is_file() {
return Ok(());
}
match status.current_tab_mut().menu_mode {
Menu::InputSimple(_) => {
status.menu.input.delete_left();
}
Menu::InputCompleted(_) => {
status.menu.input.delete_left();
status.menu.completion_reset();
}
_ => (),
}
Ok(())
}
pub fn key_home(status: &mut Status) -> Result<()> {
if status.focus.is_file() {
let tab = status.current_tab_mut();
match tab.display_mode {
Display::Directory => tab.normal_go_top(),
Display::Preview => tab.preview_go_top(),
Display::Tree => tab.tree_go_to_root()?,
Display::Fuzzy => status.fuzzy_start()?,
};
status.update_second_pane_for_preview()
} else {
match status.current_tab().menu_mode {
Menu::InputSimple(_) | Menu::InputCompleted(_) => status.menu.input.cursor_start(),
Menu::Navigate(navigate) => status.menu.set_index(0, navigate),
_ => (),
}
Ok(())
}
}
pub fn end(status: &mut Status) -> Result<()> {
if status.focus.is_file() {
let tab = status.current_tab_mut();
match tab.display_mode {
Display::Directory => tab.normal_go_bottom(),
Display::Preview => tab.preview_go_bottom(),
Display::Tree => tab.tree_go_to_bottom_leaf(),
Display::Fuzzy => status.fuzzy_end()?,
};
status.update_second_pane_for_preview()?;
} else {
match status.current_tab().menu_mode {
Menu::InputSimple(_) | Menu::InputCompleted(_) => status.menu.input.cursor_end(),
Menu::Navigate(navigate) => status.menu.select_last(navigate),
_ => (),
}
}
Ok(())
}
pub fn page_up(status: &mut Status) -> Result<()> {
if status.focus.is_file() {
Self::file_page_up(status)?;
} else {
let tab = status.current_tab_mut();
match tab.menu_mode {
Menu::Nothing => Self::file_page_up(status)?,
Menu::Navigate(navigate) => status.menu.page_up(navigate),
Menu::InputCompleted(input_completed) => {
for _ in 0..10 {
status.menu.completion_prev(input_completed)
}
if matches!(input_completed, InputCompleted::Search) {
status.follow_search()?;
}
}
Menu::NeedConfirmation(need_confirmation)
if need_confirmation.use_flagged_files() =>
{
for _ in 0..10 {
status.menu.prev(Navigate::Flagged)
}
}
Menu::NeedConfirmation(NeedConfirmation::EmptyTrash) => {
for _ in 0..10 {
status.menu.prev(Navigate::Trash)
}
}
Menu::NeedConfirmation(NeedConfirmation::BulkAction) => {
for _ in 0..10 {
status.menu.bulk.prev()
}
status.menu.window.scroll_to(status.menu.bulk.index())
}
_ => (),
};
}
Ok(())
}
fn file_page_up(status: &mut Status) -> Result<()> {
let tab = status.current_tab_mut();
match tab.display_mode {
Display::Directory => {
tab.normal_page_up();
status.update_second_pane_for_preview()?;
}
Display::Preview => tab.preview_page_up(),
Display::Tree => {
tab.tree_page_up();
status.update_second_pane_for_preview()?;
}
Display::Fuzzy => status.fuzzy_navigate(FuzzyDirection::PageUp)?,
};
Ok(())
}
pub fn page_down(status: &mut Status) -> Result<()> {
if status.focus.is_file() {
Self::file_page_down(status)?;
} else {
let tab = status.current_tab_mut();
match tab.menu_mode {
Menu::Nothing => Self::file_page_down(status)?,
Menu::Navigate(navigate) => status.menu.page_down(navigate),
Menu::InputCompleted(input_completed) => {
for _ in 0..10 {
status.menu.completion_next(input_completed)
}
if matches!(input_completed, InputCompleted::Search) {
status.follow_search()?;
}
}
Menu::NeedConfirmation(need_confirmation)
if need_confirmation.use_flagged_files() =>
{
for _ in 0..10 {
status.menu.next(Navigate::Flagged)
}
}
Menu::NeedConfirmation(NeedConfirmation::EmptyTrash) => {
for _ in 0..10 {
status.menu.next(Navigate::Trash)
}
}
Menu::NeedConfirmation(NeedConfirmation::BulkAction) => {
for _ in 0..10 {
status.menu.bulk.next()
}
status.menu.window.scroll_to(status.menu.bulk.index())
}
_ => (),
};
}
Ok(())
}
fn file_page_down(status: &mut Status) -> Result<()> {
let tab = status.current_tab_mut();
match tab.display_mode {
Display::Directory => {
tab.normal_page_down();
status.update_second_pane_for_preview()?;
}
Display::Preview => tab.preview_page_down(),
Display::Tree => {
tab.tree_page_down();
status.update_second_pane_for_preview()?;
}
Display::Fuzzy => status.fuzzy_navigate(FuzzyDirection::PageDown)?,
};
Ok(())
}
pub fn enter(status: &mut Status, binds: &Bindings) -> Result<()> {
if status.focus.is_file() {
Self::enter_file(status)
} else {
LeaveMenu::leave_menu(status, binds)
}
}
pub fn tab(status: &mut Status) -> Result<()> {
if status.focus.is_file() {
status.next()
} else if let Menu::InputCompleted(input_completed) = status.current_tab_mut().menu_mode {
status.complete_tab(input_completed)?;
}
Ok(())
}
pub fn fuzzyfind(status: &mut Status) -> Result<()> {
status.force_clear();
status.fuzzy_init(FuzzyKind::File);
status.current_tab_mut().set_display_mode(Display::Fuzzy);
status.fuzzy_find_files()?;
status.update_second_pane_for_preview()
}
pub fn fuzzyfind_line(status: &mut Status) -> Result<()> {
status.force_clear();
status.fuzzy_init(FuzzyKind::Line);
status.current_tab_mut().set_display_mode(Display::Fuzzy);
status.fuzzy_find_lines()
}
pub fn fuzzyfind_help(status: &mut Status, binds: &Bindings) -> Result<()> {
status.fuzzy_init(FuzzyKind::Action);
status.current_tab_mut().set_display_mode(Display::Fuzzy);
let help = help_string(binds, &status.internal_settings.opener);
status.fuzzy_help(help)?;
Ok(())
}
pub fn copy_content(status: &Status) -> Result<()> {
if !status.focus.is_file() {
return Ok(());
}
match status.current_tab().display_mode {
Display::Tree | Display::Directory => {
let Some(path) = status.current_tab().selected_path() else {
return Ok(());
};
content_to_clipboard(&path);
}
_ => return Ok(()),
}
Ok(())
}
pub fn copy_filename(status: &Status) -> Result<()> {
if !status.focus.is_file() {
return Ok(());
}
match status.current_tab().display_mode {
Display::Tree | Display::Directory => {
let Some(path) = status.current_tab().selected_path() else {
return Ok(());
};
filename_to_clipboard(&path);
}
_ => return Ok(()),
}
Ok(())
}
pub fn copy_filepath(status: &Status) -> Result<()> {
if !status.focus.is_file() {
return Ok(());
}
match status.current_tab().display_mode {
Display::Tree | Display::Directory => {
let Some(path) = status.current_tab().selected_path() else {
return Ok(());
};
filepath_to_clipboard(&path);
}
_ => return Ok(()),
}
Ok(())
}
pub fn trash_move_file(status: &mut Status) -> Result<()> {
if !status.focus.is_file() {
return Ok(());
}
if status.menu.flagged.is_empty() {
Self::toggle_flag(status)?;
}
status.menu.trash_and_inform()?;
status.current_tab_mut().refresh_view()?;
Ok(())
}
pub fn trash_empty(status: &mut Status) -> Result<()> {
if matches!(
status.current_tab().menu_mode,
Menu::NeedConfirmation(NeedConfirmation::EmptyTrash)
) {
status.reset_menu_mode()?;
} else {
status.menu.trash.update()?;
status.set_menu_mode(
status.index,
Menu::NeedConfirmation(NeedConfirmation::EmptyTrash),
)?;
}
Ok(())
}
pub fn trash_open(status: &mut Status) -> Result<()> {
if matches!(
status.current_tab().menu_mode,
Menu::Navigate(Navigate::Trash)
) {
status.reset_menu_mode()?;
} else {
status.menu.trash.update()?;
status.set_menu_mode(status.index, Menu::Navigate(Navigate::Trash))?;
}
Ok(())
}
pub fn trash_restore(status: &mut Status) -> Result<()> {
LeaveMenu::trash(status)
}
pub fn open_config(status: &mut Status) -> Result<()> {
if !status.focus.is_file() {
return Ok(());
}
match status.open_single_file(&path::PathBuf::from(tilde(CONFIG_PATH).to_string())) {
Ok(_) => log_line!("Opened the config file {CONFIG_PATH}"),
Err(e) => log_info!("Error opening {:?}: the config file {}", CONFIG_PATH, e),
}
Ok(())
}
pub fn compress(status: &mut Status) -> Result<()> {
if matches!(
status.current_tab().menu_mode,
Menu::Navigate(Navigate::Compress)
) {
status.reset_menu_mode()?;
} else {
if status.menu.compression.is_empty() {
status.menu.compression.setup();
}
status.set_menu_mode(status.index, Menu::Navigate(Navigate::Compress))?;
}
Ok(())
}
pub fn context(status: &mut Status) -> Result<()> {
if matches!(
status.current_tab().display_mode,
Display::Fuzzy | Display::Preview
) {
return Ok(());
}
if matches!(
status.current_tab().menu_mode,
Menu::Navigate(Navigate::Context)
) {
status.reset_menu_mode()?;
} else {
if status.menu.context.is_empty() {
status.menu.context.setup();
} else {
status.menu.context.reset();
}
status.set_menu_mode(status.index, Menu::Navigate(Navigate::Context))?;
}
Ok(())
}
pub fn action(status: &mut Status) -> Result<()> {
if matches!(
status.current_tab().menu_mode,
Menu::InputCompleted(InputCompleted::Action)
) {
status.reset_menu_mode()?;
} else {
status.set_menu_mode(status.index, Menu::InputCompleted(InputCompleted::Action))?;
status.menu.completion.reset();
}
Ok(())
}
pub fn remote_mount(status: &mut Status) -> Result<()> {
if matches!(
status.current_tab().menu_mode,
Menu::InputSimple(InputSimple::Remote)
) {
status.reset_menu_mode()?;
}
status.set_menu_mode(status.index, Menu::InputSimple(InputSimple::Remote))
}
pub fn cloud_drive(status: &mut Status) -> Result<()> {
status.cloud_open()
}
pub fn cloud_enter_newdir_mode(status: &mut Status) -> Result<()> {
status.cloud_enter_newdir_mode()
}
pub fn cloud_enter_delete_mode(status: &mut Status) -> Result<()> {
status.cloud_enter_delete_mode()
}
pub fn select_pane(status: &mut Status, col: u16) -> Result<()> {
status.select_tab_from_col(col)
}
pub fn focus_go_left(status: &mut Status) -> Result<()> {
match status.focus {
Focus::LeftMenu | Focus::LeftFile => (),
Focus::RightFile => {
status.index = 0;
status.focus = Focus::LeftFile;
}
Focus::RightMenu => {
status.index = 0;
if matches!(status.tabs[0].menu_mode, Menu::Nothing) {
status.focus = Focus::LeftFile;
} else {
status.focus = Focus::LeftMenu;
}
}
}
Ok(())
}
pub fn focus_go_right(status: &mut Status) -> Result<()> {
match status.focus {
Focus::RightMenu | Focus::RightFile => (),
Focus::LeftFile => {
status.index = 1;
status.focus = Focus::RightFile;
}
Focus::LeftMenu => {
status.index = 1;
if matches!(status.tabs[1].menu_mode, Menu::Nothing) {
status.focus = Focus::RightFile;
} else {
status.focus = Focus::RightMenu;
}
}
}
Ok(())
}
pub fn focus_go_down(status: &mut Status) -> Result<()> {
match status.focus {
Focus::RightMenu | Focus::LeftMenu => (),
Focus::LeftFile => {
if !matches!(status.tabs[0].menu_mode, Menu::Nothing) {
status.focus = Focus::LeftMenu;
}
}
Focus::RightFile => {
if !matches!(status.tabs[1].menu_mode, Menu::Nothing) {
status.focus = Focus::RightMenu;
}
}
}
Ok(())
}
pub fn focus_go_up(status: &mut Status) -> Result<()> {
match status.focus {
Focus::LeftFile | Focus::RightFile => (),
Focus::LeftMenu => status.focus = Focus::LeftFile,
Focus::RightMenu => status.focus = Focus::RightFile,
}
Ok(())
}
pub fn sync_ltr(status: &mut Status) -> Result<()> {
if status.focus.is_file() {
status.sync_tabs(Direction::LeftToRight)?;
}
Ok(())
}
pub fn bulk_confirm(status: &mut Status) -> Result<()> {
status.bulk_execute()
}
pub fn file_copied(status: &mut Status, done_copy_moves: Vec<DoneCopyMove>) -> Result<()> {
log_info!(
"file copied - pool: {pool:?} - done_copy_moves: {done_copy_moves:?}",
pool = status.internal_settings.copy_file_queue
);
status.internal_settings.copy_file_remove_head()?;
if status.internal_settings.copy_file_queue.is_empty() {
status.internal_settings.unset_copy_progress()
} else {
status.copy_next_file_in_queue()?;
}
for done_copy_move in &done_copy_moves {
log_line!("{done_copy_move}");
if !done_copy_move.copy_move.is_copy() {
status.rename_marks(&done_copy_move.from, &done_copy_move.final_to)?;
}
}
Ok(())
}
pub fn display_copy_progress(status: &mut Status, content: InMemoryTerm) -> Result<()> {
status.internal_settings.store_copy_progress(content);
Ok(())
}
pub fn check_preview_fuzzy_tick(status: &mut Status) -> Result<()> {
status.fuzzy_tick();
status.check_preview()
}
pub fn visual(status: &mut Status) -> Result<()> {
status.current_tab_mut().toggle_visual();
status.toggle_flag_visual();
Ok(())
}
pub fn mount(status: &mut Status) -> Result<()> {
if matches!(
status.current_tab().menu_mode,
Menu::Navigate(Navigate::Mount)
) {
status.reset_menu_mode()?;
} else if lsblk_and_udisksctl_installed() {
status.menu.mount.update(status.internal_settings.disks())?;
status.set_menu_mode(status.index, Menu::Navigate(Navigate::Mount))?;
}
Ok(())
}
pub fn custom(status: &mut Status, input_string: &str) -> Result<()> {
status.run_custom_command(input_string)
}
pub fn parse_rpc(status: &mut Status, ipc_msg: String) -> Result<()> {
status.parse_ipc(ipc_msg)
}
}