use std::io::stdout;
use std::panic;
use std::process::exit;
use std::sync::{mpsc, Arc};
#[cfg(debug_assertions)]
use std::backtrace;
use anyhow::Result;
use clap::Parser;
use crossterm::{
cursor,
event::{DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste, EnableMouseCapture},
execute,
terminal::{disable_raw_mode, Clear, ClearType},
};
use parking_lot::Mutex;
use ratatui::{init as init_term, DefaultTerminal};
use crate::app::{Displayer, Refresher, Status};
use crate::common::{clear_input_socket_files, clear_tmp_files, save_final_path, CONFIG_PATH};
use crate::config::{load_config, set_configurable_static, Config, IS_LOGGING};
use crate::event::{remove_socket, EventDispatcher, EventReader, FmEvents};
use crate::io::{Args, FMLogger, Opener};
use crate::log_info;
pub struct FM {
event_reader: EventReader,
event_dispatcher: EventDispatcher,
status: Arc<Mutex<Status>>,
refresher: Refresher,
displayer: Displayer,
}
impl FM {
pub fn start() -> Result<Self> {
Self::set_panic_hook();
let (mut config, start_folder) = Self::early_exit()?;
log_info!("start folder: {start_folder}");
let plugins = std::mem::take(&mut config.plugins);
let theme = std::mem::take(&mut config.theme);
set_configurable_static(&start_folder, plugins, theme)?;
Self::build(config)
}
fn set_panic_hook() {
panic::set_hook(Box::new(|traceback| {
clear_tmp_files();
let _ = disable_raw_mode();
let _ = execute!(
stdout(),
cursor::Show,
DisableMouseCapture,
DisableBracketedPaste
);
if cfg!(debug_assertions) {
if let Some(payload) = traceback.payload().downcast_ref::<&str>() {
eprintln!("Traceback: {payload}",);
} else if let Some(payload) = traceback.payload().downcast_ref::<String>() {
eprintln!("Traceback: {payload}",);
} else {
eprintln!("Traceback:{traceback:?}");
}
if let Some(location) = traceback.location() {
eprintln!("At {location}");
}
#[cfg(debug_assertions)]
eprintln!("{}", backtrace::Backtrace::capture());
} else {
eprintln!("fm exited unexpectedly.");
}
}));
}
fn early_exit() -> Result<(Config, String)> {
let args = Args::parse();
IS_LOGGING.get_or_init(|| args.log);
if args.log {
FMLogger::default().init()?;
}
log_info!("args {args:#?}");
let Ok(config) = load_config(CONFIG_PATH) else {
Self::exit_wrong_config()
};
Ok((config, args.path))
}
pub fn exit_wrong_config() -> ! {
eprintln!("Couldn't load the config file at {CONFIG_PATH}. See https://raw.githubusercontent.com/qkzk/fm/master/config_files/fm/config.yaml for an example.");
log_info!("Couldn't read the config file {CONFIG_PATH}");
exit(1)
}
fn build(config: Config) -> Result<Self> {
let (fm_sender, fm_receiver) = mpsc::channel::<FmEvents>();
let event_reader = EventReader::new(fm_receiver);
let fm_sender = Arc::new(fm_sender);
let term = Self::init_term()?;
let status = Status::arc_mutex_new(
term.size()?,
Opener::default(),
&config.binds,
fm_sender.clone(),
)?;
let event_dispatcher = EventDispatcher::new(config.binds);
let refresher = Refresher::new(fm_sender);
let displayer = Displayer::new(term, status.clone());
Ok(Self {
event_reader,
event_dispatcher,
status,
refresher,
displayer,
})
}
fn init_term() -> Result<DefaultTerminal> {
let term = init_term();
execute!(stdout(), EnableMouseCapture, EnableBracketedPaste)?;
Ok(term)
}
fn update(&mut self, event: FmEvents) -> Result<()> {
let mut status = self.status.lock();
self.event_dispatcher.dispatch(&mut status, event)?;
Ok(())
}
fn must_quit(&self) -> Result<bool> {
let status = self.status.lock();
Ok(status.must_quit())
}
pub fn run(mut self) -> Result<Self> {
while !self.must_quit()? {
self.update(self.event_reader.poll_event())?;
}
Ok(self)
}
fn clear() -> Result<()> {
execute!(stdout(), Clear(ClearType::All))?;
Ok(())
}
fn disable_mouse_capture() -> Result<()> {
execute!(stdout(), DisableMouseCapture, DisableBracketedPaste)?;
Ok(())
}
pub fn quit(self) -> Result<()> {
let final_path = self.status.lock().current_tab_path_str().to_owned();
clear_tmp_files();
remove_socket(&self.event_reader.socket_path);
drop(self.event_reader);
drop(self.event_dispatcher);
self.displayer.quit();
self.refresher.quit();
let status = self.status.lock();
status.previewer.quit();
if status.internal_settings.clear_before_quit {
Self::clear()?;
}
drop(status);
drop(self.status);
clear_input_socket_files()?;
Self::disable_mouse_capture()?;
save_final_path(&final_path);
Ok(())
}
}