1use std::io::stdout;
2use std::panic;
3use std::process::exit;
4use std::sync::{mpsc, Arc};
5
6#[cfg(debug_assertions)]
7use std::backtrace;
8
9use anyhow::Result;
10use clap::Parser;
11use crossterm::{
12 cursor,
13 event::{DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste, EnableMouseCapture},
14 execute,
15 terminal::{disable_raw_mode, Clear, ClearType},
16};
17use parking_lot::Mutex;
18use ratatui::{init as init_term, DefaultTerminal};
19
20use crate::app::{Displayer, Refresher, Status};
21use crate::common::{clear_input_socket_files, clear_tmp_files, save_final_path, CONFIG_PATH};
22use crate::config::{load_config, set_configurable_static, Config, IS_LOGGING};
23use crate::event::{remove_socket, EventDispatcher, EventReader, FmEvents};
24use crate::io::{Args, FMLogger, Opener};
25use crate::log_info;
26
27pub struct FM {
38 event_reader: EventReader,
40 event_dispatcher: EventDispatcher,
42 status: Arc<Mutex<Status>>,
44 refresher: Refresher,
47 displayer: Displayer,
51}
52
53impl FM {
54 pub fn start() -> Result<Self> {
68 Self::set_panic_hook();
69 let (mut config, start_folder) = Self::early_exit()?;
70 log_info!("start folder: {start_folder}");
71 let plugins = std::mem::take(&mut config.plugins);
72 set_configurable_static(&start_folder, plugins)?;
73 Self::build(config)
74 }
75
76 fn set_panic_hook() {
83 panic::set_hook(Box::new(|traceback| {
84 clear_tmp_files();
85 let _ = disable_raw_mode();
86 let _ = execute!(
87 stdout(),
88 cursor::Show,
89 DisableMouseCapture,
90 DisableBracketedPaste
91 );
92
93 if cfg!(debug_assertions) {
94 if let Some(payload) = traceback.payload().downcast_ref::<&str>() {
95 eprintln!("Traceback: {payload}",);
96 } else if let Some(payload) = traceback.payload().downcast_ref::<String>() {
97 eprintln!("Traceback: {payload}",);
98 } else {
99 eprintln!("Traceback:{traceback:?}");
100 }
101 if let Some(location) = traceback.location() {
102 eprintln!("At {location}");
103 }
104 #[cfg(debug_assertions)]
105 eprintln!("{}", backtrace::Backtrace::capture());
106 } else {
107 eprintln!("fm exited unexpectedly.");
108 }
109 }));
110 }
111
112 fn early_exit() -> Result<(Config, String)> {
116 let args = Args::parse();
117 IS_LOGGING.get_or_init(|| args.log);
118 if args.log {
119 FMLogger::default().init()?;
120 }
121 log_info!("args {args:#?}");
122 let Ok(config) = load_config(CONFIG_PATH) else {
123 Self::exit_wrong_config()
124 };
125 Ok((config, args.path))
126 }
127
128 pub fn exit_wrong_config() -> ! {
131 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.");
132 log_info!("Couldn't read the config file {CONFIG_PATH}");
133 exit(1)
134 }
135
136 fn build(config: Config) -> Result<Self> {
143 let (fm_sender, fm_receiver) = mpsc::channel::<FmEvents>();
144 let event_reader = EventReader::new(fm_receiver);
145 let fm_sender = Arc::new(fm_sender);
146 let term = Self::init_term()?;
147 let status = Status::arc_mutex_new(
148 term.size()?,
149 Opener::default(),
150 &config.binds,
151 fm_sender.clone(),
152 )?;
153 let event_dispatcher = EventDispatcher::new(config.binds);
154 let refresher = Refresher::new(fm_sender);
155 let displayer = Displayer::new(term, status.clone());
156
157 Ok(Self {
158 event_reader,
159 event_dispatcher,
160 status,
161 refresher,
162 displayer,
163 })
164 }
165
166 fn init_term() -> Result<DefaultTerminal> {
167 let term = init_term();
168 execute!(stdout(), EnableMouseCapture, EnableBracketedPaste)?;
169 Ok(term)
170 }
171
172 fn update(&mut self, event: FmEvents) -> Result<()> {
175 let mut status = self.status.lock();
176 self.event_dispatcher.dispatch(&mut status, event)?;
177
178 Ok(())
179 }
180
181 fn must_quit(&self) -> Result<bool> {
183 let status = self.status.lock();
184 Ok(status.must_quit())
185 }
186
187 pub fn run(mut self) -> Result<Self> {
189 while !self.must_quit()? {
190 self.update(self.event_reader.poll_event())?;
191 }
192 Ok(self)
193 }
194
195 fn clear() -> Result<()> {
197 execute!(stdout(), Clear(ClearType::All))?;
198 Ok(())
199 }
200
201 fn disable_mouse_capture() -> Result<()> {
203 execute!(stdout(), DisableMouseCapture, DisableBracketedPaste)?;
204 Ok(())
205 }
206
207 pub fn quit(self) -> Result<()> {
219 let final_path = self.status.lock().current_tab_path_str().to_owned();
220
221 clear_tmp_files();
222
223 remove_socket(&self.event_reader.socket_path);
224 drop(self.event_reader);
225 drop(self.event_dispatcher);
226 self.displayer.quit();
227 self.refresher.quit();
228 let status = self.status.lock();
229 status.previewer.quit();
230 if status.internal_settings.clear_before_quit {
231 Self::clear()?;
232 }
233 drop(status);
234
235 drop(self.status);
236 clear_input_socket_files()?;
237 Self::disable_mouse_capture()?;
238 save_final_path(&final_path);
239 Ok(())
240 }
241}