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 let theme = std::mem::take(&mut config.theme);
73 set_configurable_static(&start_folder, plugins, theme)?;
74 Self::build(config)
75 }
76
77 fn set_panic_hook() {
84 panic::set_hook(Box::new(|traceback| {
85 clear_tmp_files();
86 let _ = disable_raw_mode();
87 let _ = execute!(
88 stdout(),
89 cursor::Show,
90 DisableMouseCapture,
91 DisableBracketedPaste
92 );
93
94 if cfg!(debug_assertions) {
95 if let Some(payload) = traceback.payload().downcast_ref::<&str>() {
96 eprintln!("Traceback: {payload}",);
97 } else if let Some(payload) = traceback.payload().downcast_ref::<String>() {
98 eprintln!("Traceback: {payload}",);
99 } else {
100 eprintln!("Traceback:{traceback:?}");
101 }
102 if let Some(location) = traceback.location() {
103 eprintln!("At {location}");
104 }
105 #[cfg(debug_assertions)]
106 eprintln!("{}", backtrace::Backtrace::capture());
107 } else {
108 eprintln!("fm exited unexpectedly.");
109 }
110 }));
111 }
112
113 fn early_exit() -> Result<(Config, String)> {
117 let args = Args::parse();
118 IS_LOGGING.get_or_init(|| args.log);
119 if args.log {
120 FMLogger::default().init()?;
121 }
122 log_info!("args {args:#?}");
123 let Ok(config) = load_config(CONFIG_PATH) else {
124 Self::exit_wrong_config()
125 };
126 Ok((config, args.path))
127 }
128
129 pub fn exit_wrong_config() -> ! {
132 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.");
133 log_info!("Couldn't read the config file {CONFIG_PATH}");
134 exit(1)
135 }
136
137 fn build(config: Config) -> Result<Self> {
144 let (fm_sender, fm_receiver) = mpsc::channel::<FmEvents>();
145 let event_reader = EventReader::new(fm_receiver);
146 let fm_sender = Arc::new(fm_sender);
147 let term = Self::init_term()?;
148 let status = Status::arc_mutex_new(
149 term.size()?,
150 Opener::default(),
151 &config.binds,
152 fm_sender.clone(),
153 )?;
154 let event_dispatcher = EventDispatcher::new(config.binds);
155 let refresher = Refresher::new(fm_sender);
156 let displayer = Displayer::new(term, status.clone());
157
158 Ok(Self {
159 event_reader,
160 event_dispatcher,
161 status,
162 refresher,
163 displayer,
164 })
165 }
166
167 fn init_term() -> Result<DefaultTerminal> {
168 let term = init_term();
169 execute!(stdout(), EnableMouseCapture, EnableBracketedPaste)?;
170 Ok(term)
171 }
172
173 fn update(&mut self, event: FmEvents) -> Result<()> {
176 let mut status = self.status.lock();
177 self.event_dispatcher.dispatch(&mut status, event)?;
178
179 Ok(())
180 }
181
182 fn must_quit(&self) -> Result<bool> {
184 let status = self.status.lock();
185 Ok(status.must_quit())
186 }
187
188 pub fn run(mut self) -> Result<Self> {
190 while !self.must_quit()? {
191 self.update(self.event_reader.poll_event())?;
192 }
193 Ok(self)
194 }
195
196 fn clear() -> Result<()> {
198 execute!(stdout(), Clear(ClearType::All))?;
199 Ok(())
200 }
201
202 fn disable_mouse_capture() -> Result<()> {
204 execute!(stdout(), DisableMouseCapture, DisableBracketedPaste)?;
205 Ok(())
206 }
207
208 pub fn quit(self) -> Result<()> {
220 let final_path = self.status.lock().current_tab_path_str().to_owned();
221
222 clear_tmp_files();
223
224 remove_socket(&self.event_reader.socket_path);
225 drop(self.event_reader);
226 drop(self.event_dispatcher);
227 self.displayer.quit();
228 self.refresher.quit();
229 let status = self.status.lock();
230 status.previewer.quit();
231 if status.internal_settings.clear_before_quit {
232 Self::clear()?;
233 }
234 drop(status);
235
236 drop(self.status);
237 clear_input_socket_files()?;
238 Self::disable_mouse_capture()?;
239 save_final_path(&final_path);
240 Ok(())
241 }
242}