use std::collections::{HashMap, HashSet};
use std::path::{Path, PathBuf};
use std::sync::{mpsc::Sender, Arc};
use anyhow::{bail, Result};
use clap::Parser;
use indicatif::InMemoryTerm;
use ratatui::buffer::Buffer;
use ratatui::layout::Size;
use sysinfo::Disks;
use crate::common::{is_in_path, open_in_current_neovim, set_clipboard, NVIM, SS};
use crate::config::Bindings;
use crate::event::FmEvents;
use crate::io::{
execute_and_output, read_rect_from_buffer, Args, Cursor, CursorDirection, Extension, External,
Opener,
};
use crate::modes::{copy_move, extract_extension, Content, Flagged};
pub struct InternalSettings {
pub force_clear: bool,
pub must_quit: bool,
pub nvim_server: String,
pub opener: Opener,
pub size: Size,
pub disks: Disks,
pub inside_neovim: bool,
pub copy_file_queue: Vec<(Vec<PathBuf>, PathBuf)>,
pub in_mem_progress: Option<InMemoryTerm>,
is_disabled: bool,
pub clear_before_quit: bool,
pub cursor: Cursor,
pub last_buffer: Option<Buffer>,
}
impl InternalSettings {
pub fn new(opener: Opener, size: Size, disks: Disks, binds: &Bindings) -> Self {
let args = Args::parse();
let force_clear = false;
let must_quit = false;
let nvim_server = args.server.clone();
let inside_neovim = args.neovim;
let copy_file_queue = vec![];
let in_mem_progress = None;
let is_disabled = false;
let clear_before_quit = false;
let cursor = Cursor::new(binds);
let last_buffer = None;
Self {
force_clear,
must_quit,
nvim_server,
opener,
disks,
size,
inside_neovim,
copy_file_queue,
in_mem_progress,
is_disabled,
clear_before_quit,
cursor,
last_buffer,
}
}
#[inline]
pub fn term_size(&self) -> Size {
self.size
}
pub fn update_size(&mut self, width: u16, height: u16) {
self.size = Size::from((width, height))
}
pub fn force_clear(&mut self) {
self.force_clear = true;
}
pub fn reset_clear(&mut self) {
self.force_clear = false;
}
pub fn should_be_cleared(&self) -> bool {
self.force_clear
}
pub fn disks(&mut self) -> &Disks {
self.disks.refresh(true);
&self.disks
}
pub fn mount_points_vec(&mut self) -> Vec<&Path> {
self.disks().iter().map(|d| d.mount_point()).collect()
}
pub fn mount_points_set(&self) -> HashSet<&Path> {
self.disks
.list()
.iter()
.map(|disk| disk.mount_point())
.collect()
}
pub fn update_nvim_listen_address(&mut self) {
if let Ok(nvim_listen_address) = std::env::var("NVIM_LISTEN_ADDRESS") {
self.nvim_server = nvim_listen_address;
} else if let Ok(nvim_listen_address) = Self::parse_nvim_address_from_ss_output() {
self.nvim_server = nvim_listen_address;
}
}
fn parse_nvim_address_from_ss_output() -> Result<String> {
if !is_in_path(SS) {
bail!("{SS} isn't installed");
}
if let Ok(output) = execute_and_output(SS, ["-l"]) {
let output = String::from_utf8(output.stdout).unwrap_or_default();
let content: String = output
.split(&['\n', '\t', ' '])
.find(|w| w.contains(NVIM))
.unwrap_or("")
.to_string();
if !content.is_empty() {
return Ok(content);
}
}
bail!("Couldn't get nvim listen address from `ss` output")
}
pub fn copy_file_remove_head(&mut self) -> Result<()> {
if !self.copy_file_queue.is_empty() {
self.copy_file_queue.remove(0);
}
Ok(())
}
pub fn copy_next_file_in_queue(
&mut self,
fm_sender: Arc<Sender<FmEvents>>,
width: u16,
) -> Result<()> {
let (sources, dest) = self.copy_file_queue[0].clone();
let height = self.term_size().height;
let in_mem = copy_move(
crate::modes::CopyMove::Copy,
sources,
dest,
width,
height,
fm_sender,
)?;
self.store_copy_progress(in_mem);
Ok(())
}
pub fn store_copy_progress(&mut self, in_mem_progress_bar: InMemoryTerm) {
self.in_mem_progress = Some(in_mem_progress_bar);
}
pub fn unset_copy_progress(&mut self) {
self.in_mem_progress = None;
}
pub fn disable_display(&mut self) {
self.is_disabled = true;
}
pub fn enable_display(&mut self) {
if !self.is_disabled() {
return;
}
self.is_disabled = false;
self.force_clear();
self.clear_before_quit = true;
}
pub fn is_disabled(&self) -> bool {
self.is_disabled
}
pub fn open_in_window<P>(&mut self, args: &[&str], current_path: P) -> Result<()>
where
P: AsRef<Path>,
{
self.disable_display();
External::open_command_in_window(args, current_path)?;
self.enable_display();
Ok(())
}
fn should_this_file_be_opened_in_neovim(&self, path: &Path) -> bool {
matches!(Extension::matcher(extract_extension(path)), Extension::Text)
}
pub fn open_single_file<P>(&mut self, path: &Path, current_path: P) -> Result<()>
where
P: AsRef<Path>,
{
if self.inside_neovim && self.should_this_file_be_opened_in_neovim(path) {
self.update_nvim_listen_address();
open_in_current_neovim(path, &self.nvim_server);
Ok(())
} else if self.opener.use_term(path) {
self.open_single_in_window(path, current_path);
Ok(())
} else {
self.opener.open_single(path)
}
}
fn open_single_in_window<P>(&mut self, path: &Path, current_path: P)
where
P: AsRef<Path>,
{
self.disable_display();
self.opener.open_in_window(path, current_path);
self.enable_display();
}
pub fn open_flagged_files<P>(&mut self, flagged: &Flagged, current_path: P) -> Result<()>
where
P: AsRef<Path>,
{
if self.inside_neovim && flagged.should_all_be_opened_in_neovim() {
self.open_multiple_in_neovim(flagged.content());
Ok(())
} else {
self.open_multiple_outside(flagged.content(), current_path)
}
}
fn open_multiple_outside<P>(&mut self, paths: &[PathBuf], current_path: P) -> Result<()>
where
P: AsRef<Path>,
{
let openers = self.opener.regroup_per_opener(paths);
if Self::all_files_opened_in_terminal(&openers) {
self.open_multiple_files_in_window(openers, current_path)
} else {
self.opener.open_multiple(openers)
}
}
fn all_files_opened_in_terminal(openers: &HashMap<External, Vec<PathBuf>>) -> bool {
openers.len() == 1 && openers.keys().next().expect("Can't be empty").use_term()
}
fn open_multiple_files_in_window<P>(
&mut self,
openers: HashMap<External, Vec<PathBuf>>,
current_path: P,
) -> Result<()>
where
P: AsRef<Path>,
{
self.disable_display();
self.opener.open_multiple_in_window(openers, current_path)?;
self.enable_display();
Ok(())
}
fn open_multiple_in_neovim(&mut self, paths: &[PathBuf]) {
self.update_nvim_listen_address();
for path in paths {
open_in_current_neovim(path, &self.nvim_server);
}
}
pub fn quit(&mut self) {
self.must_quit = true
}
pub fn format_copy_progress(&self) -> Option<String> {
let Some(copy_progress) = &self.in_mem_progress else {
return None;
};
let progress_bar = copy_progress.contents();
let nb_copy_left = self.copy_file_queue.len();
if nb_copy_left <= 1 {
Some(progress_bar)
} else {
Some(format!(
"{progress_bar} - 1 of {nb}",
nb = nb_copy_left
))
}
}
pub fn move_cursor(&mut self, direction: CursorDirection) {
self.cursor.move_cursor(direction, self.term_size());
if self.cursor.is_selecting() {
self.cursor.extend_selection();
}
}
pub fn copy_buffer_rect(&self) {
let Some(buffer) = &self.last_buffer else {
crate::log_info!("Tried to read last buffer but had nothing.");
crate::log_line!("Couldn't copy the content...");
return;
};
let Some(rect) = &self.cursor.rect() else {
return;
};
let content = read_rect_from_buffer(rect, buffer);
set_clipboard(content);
}
}