1use std::collections::{HashMap, HashSet};
2use std::path::{Path, PathBuf};
3use std::sync::{mpsc::Sender, Arc};
4
5use anyhow::{bail, Result};
6use clap::Parser;
7use indicatif::InMemoryTerm;
8use ratatui::buffer::Buffer;
9use ratatui::layout::Size;
10use sysinfo::Disks;
11
12use crate::common::{is_in_path, open_in_current_neovim, set_clipboard, NVIM, SS};
13use crate::config::Bindings;
14use crate::event::FmEvents;
15use crate::io::{
16 execute_and_output, read_rect_from_buffer, Args, Cursor, CursorDirection, Extension, External,
17 Opener,
18};
19use crate::modes::{copy_move, extract_extension, Content, Flagged};
20
21pub struct InternalSettings {
33 pub force_clear: bool,
35 pub must_quit: bool,
38 pub nvim_server: String,
40 pub opener: Opener,
42 pub size: Size,
44 pub disks: Disks,
47 pub inside_neovim: bool,
49 pub copy_file_queue: Vec<(Vec<PathBuf>, PathBuf)>,
52 pub in_mem_progress: Option<InMemoryTerm>,
54 is_disabled: bool,
56 pub clear_before_quit: bool,
58 pub cursor: Cursor,
60 pub last_buffer: Option<Buffer>,
62}
63
64impl InternalSettings {
65 pub fn new(opener: Opener, size: Size, disks: Disks, binds: &Bindings) -> Self {
67 let args = Args::parse();
68 let force_clear = false;
69 let must_quit = false;
70 let nvim_server = args.server.clone();
71 let inside_neovim = args.neovim;
72 let copy_file_queue = vec![];
73 let in_mem_progress = None;
74 let is_disabled = false;
75 let clear_before_quit = false;
76 let cursor = Cursor::new(binds);
77 let last_buffer = None;
78 Self {
79 force_clear,
80 must_quit,
81 nvim_server,
82 opener,
83 disks,
84 size,
85 inside_neovim,
86 copy_file_queue,
87 in_mem_progress,
88 is_disabled,
89 clear_before_quit,
90 cursor,
91 last_buffer,
92 }
93 }
94
95 #[inline]
96 pub fn term_size(&self) -> Size {
98 self.size
99 }
100
101 pub fn update_size(&mut self, width: u16, height: u16) {
103 self.size = Size::from((width, height))
104 }
105
106 pub fn force_clear(&mut self) {
110 self.force_clear = true;
111 }
112
113 pub fn reset_clear(&mut self) {
116 self.force_clear = false;
117 }
118
119 pub fn should_be_cleared(&self) -> bool {
121 self.force_clear
122 }
123
124 pub fn disks(&mut self) -> &Disks {
126 self.disks.refresh(true);
127 &self.disks
128 }
129
130 pub fn mount_points_vec(&mut self) -> Vec<&Path> {
133 self.disks().iter().map(|d| d.mount_point()).collect()
134 }
135
136 pub fn mount_points_set(&self) -> HashSet<&Path> {
138 self.disks
139 .list()
140 .iter()
141 .map(|disk| disk.mount_point())
142 .collect()
143 }
144
145 pub fn update_nvim_listen_address(&mut self) {
152 if let Ok(nvim_listen_address) = std::env::var("NVIM_LISTEN_ADDRESS") {
153 self.nvim_server = nvim_listen_address;
154 } else if let Ok(nvim_listen_address) = Self::parse_nvim_address_from_ss_output() {
155 self.nvim_server = nvim_listen_address;
156 }
157 }
158
159 fn parse_nvim_address_from_ss_output() -> Result<String> {
160 if !is_in_path(SS) {
161 bail!("{SS} isn't installed");
162 }
163 if let Ok(output) = execute_and_output(SS, ["-l"]) {
164 let output = String::from_utf8(output.stdout).unwrap_or_default();
165 let content: String = output
166 .split(&['\n', '\t', ' '])
167 .find(|w| w.contains(NVIM))
168 .unwrap_or("")
169 .to_string();
170 if !content.is_empty() {
171 return Ok(content);
172 }
173 }
174 bail!("Couldn't get nvim listen address from `ss` output")
175 }
176
177 pub fn copy_file_remove_head(&mut self) -> Result<()> {
179 if !self.copy_file_queue.is_empty() {
180 self.copy_file_queue.remove(0);
181 }
182 Ok(())
183 }
184
185 pub fn copy_next_file_in_queue(
188 &mut self,
189 fm_sender: Arc<Sender<FmEvents>>,
190 width: u16,
191 ) -> Result<()> {
192 let (sources, dest) = self.copy_file_queue[0].clone();
193 let height = self.term_size().height;
194 let in_mem = copy_move(
195 crate::modes::CopyMove::Copy,
196 sources,
197 dest,
198 width,
199 height,
200 fm_sender,
201 )?;
202 self.store_copy_progress(in_mem);
203 Ok(())
204 }
205
206 pub fn store_copy_progress(&mut self, in_mem_progress_bar: InMemoryTerm) {
210 self.in_mem_progress = Some(in_mem_progress_bar);
211 }
212
213 pub fn unset_copy_progress(&mut self) {
215 self.in_mem_progress = None;
216 }
217
218 pub fn disable_display(&mut self) {
221 self.is_disabled = true;
222 }
223
224 pub fn enable_display(&mut self) {
230 if !self.is_disabled() {
231 return;
232 }
233 self.is_disabled = false;
234 self.force_clear();
235 self.clear_before_quit = true;
236 }
237
238 pub fn is_disabled(&self) -> bool {
243 self.is_disabled
244 }
245
246 pub fn open_in_window<P>(&mut self, args: &[&str], current_path: P) -> Result<()>
251 where
252 P: AsRef<Path>,
253 {
254 self.disable_display();
255 External::open_command_in_window(args, current_path)?;
256 self.enable_display();
257 Ok(())
258 }
259
260 fn should_this_file_be_opened_in_neovim(&self, path: &Path) -> bool {
261 matches!(Extension::matcher(extract_extension(path)), Extension::Text)
262 }
263
264 pub fn open_single_file<P>(&mut self, path: &Path, current_path: P) -> Result<()>
269 where
270 P: AsRef<Path>,
271 {
272 if self.inside_neovim && self.should_this_file_be_opened_in_neovim(path) {
273 self.update_nvim_listen_address();
274 open_in_current_neovim(path, &self.nvim_server);
275 Ok(())
276 } else if self.opener.use_term(path) {
277 self.open_single_in_window(path, current_path);
278 Ok(())
279 } else {
280 self.opener.open_single(path)
281 }
282 }
283
284 fn open_single_in_window<P>(&mut self, path: &Path, current_path: P)
285 where
286 P: AsRef<Path>,
287 {
288 self.disable_display();
289 self.opener.open_in_window(path, current_path);
290 self.enable_display();
291 }
292
293 pub fn open_flagged_files<P>(&mut self, flagged: &Flagged, current_path: P) -> Result<()>
298 where
299 P: AsRef<Path>,
300 {
301 if self.inside_neovim && flagged.should_all_be_opened_in_neovim() {
302 self.open_multiple_in_neovim(flagged.content());
303 Ok(())
304 } else {
305 self.open_multiple_outside(flagged.content(), current_path)
306 }
307 }
308
309 fn open_multiple_outside<P>(&mut self, paths: &[PathBuf], current_path: P) -> Result<()>
310 where
311 P: AsRef<Path>,
312 {
313 let openers = self.opener.regroup_per_opener(paths);
314 if Self::all_files_opened_in_terminal(&openers) {
315 self.open_multiple_files_in_window(openers, current_path)
316 } else {
317 self.opener.open_multiple(openers)
318 }
319 }
320
321 fn all_files_opened_in_terminal(openers: &HashMap<External, Vec<PathBuf>>) -> bool {
322 openers.len() == 1 && openers.keys().next().expect("Can't be empty").use_term()
323 }
324
325 fn open_multiple_files_in_window<P>(
326 &mut self,
327 openers: HashMap<External, Vec<PathBuf>>,
328 current_path: P,
329 ) -> Result<()>
330 where
331 P: AsRef<Path>,
332 {
333 self.disable_display();
334 self.opener.open_multiple_in_window(openers, current_path)?;
335 self.enable_display();
336 Ok(())
337 }
338
339 fn open_multiple_in_neovim(&mut self, paths: &[PathBuf]) {
340 self.update_nvim_listen_address();
341 for path in paths {
342 open_in_current_neovim(path, &self.nvim_server);
343 }
344 }
345
346 pub fn quit(&mut self) {
350 self.must_quit = true
351 }
352
353 pub fn format_copy_progress(&self) -> Option<String> {
356 let Some(copy_progress) = &self.in_mem_progress else {
357 return None;
358 };
359 let progress_bar = copy_progress.contents();
360 let nb_copy_left = self.copy_file_queue.len();
361 if nb_copy_left <= 1 {
362 Some(progress_bar)
363 } else {
364 Some(format!(
365 "{progress_bar} - 1 of {nb}",
366 nb = nb_copy_left
367 ))
368 }
369 }
370
371 pub fn move_cursor(&mut self, direction: CursorDirection) {
375 self.cursor.move_cursor(direction, self.term_size());
376 if self.cursor.is_selecting() {
377 self.cursor.extend_selection();
378 }
379 }
380
381 pub fn copy_buffer_rect(&self) {
383 let Some(buffer) = &self.last_buffer else {
384 crate::log_info!("Tried to read last buffer but had nothing.");
385 crate::log_line!("Couldn't copy the content...");
386 return;
387 };
388 let Some(rect) = &self.cursor.rect() else {
389 return;
390 };
391 let content = read_rect_from_buffer(rect, buffer);
392 set_clipboard(content);
393 }
394}