fm/app/
internal_settings.rs1use std::collections::{HashMap, HashSet};
2use std::path::{Path, PathBuf};
3use std::sync::{mpsc::Sender, Arc};
4
5use anyhow::{anyhow, Result};
6use clap::Parser;
7use indicatif::InMemoryTerm;
8use ratatui::layout::Size;
9use sysinfo::Disks;
10
11use crate::common::{is_in_path, open_in_current_neovim, NVIM, SS};
12use crate::event::FmEvents;
13use crate::io::{execute_and_output, Args, Extension, External, Opener};
14use crate::modes::{copy_move, extract_extension, Content, Flagged};
15
16pub struct InternalSettings {
28 pub force_clear: bool,
30 pub must_quit: bool,
33 pub nvim_server: String,
35 pub opener: Opener,
37 pub size: Size,
39 pub disks: Disks,
42 pub inside_neovim: bool,
44 pub copy_file_queue: Vec<(Vec<PathBuf>, PathBuf)>,
47 pub in_mem_progress: Option<InMemoryTerm>,
49 is_disabled: bool,
51 pub clear_before_quit: bool,
53}
54
55impl InternalSettings {
56 pub fn new(opener: Opener, size: Size, disks: Disks) -> Self {
57 let args = Args::parse();
58 let force_clear = false;
59 let must_quit = false;
60 let nvim_server = args.server.clone();
61 let inside_neovim = args.neovim;
62 let copy_file_queue = vec![];
63 let in_mem_progress = None;
64 let is_disabled = false;
65 let clear_before_quit = false;
66 Self {
67 force_clear,
68 must_quit,
69 nvim_server,
70 opener,
71 disks,
72 size,
73 inside_neovim,
74 copy_file_queue,
75 in_mem_progress,
76 is_disabled,
77 clear_before_quit,
78 }
79 }
80
81 pub fn term_size(&self) -> Size {
83 self.size
84 }
85
86 pub fn update_size(&mut self, width: u16, height: u16) {
87 self.size = Size::from((width, height))
88 }
89
90 pub fn force_clear(&mut self) {
94 self.force_clear = true;
95 }
96
97 pub fn reset_clear(&mut self) {
98 self.force_clear = false;
99 }
100
101 pub fn should_be_cleared(&self) -> bool {
102 self.force_clear
103 }
104
105 pub fn disks(&mut self) -> &Disks {
106 self.disks.refresh_list();
107 &self.disks
108 }
109
110 pub fn mount_points_vec(&mut self) -> Vec<&Path> {
111 self.disks().iter().map(|d| d.mount_point()).collect()
112 }
113
114 pub fn mount_points_set(&self) -> HashSet<&Path> {
115 self.disks
116 .list()
117 .iter()
118 .map(|disk| disk.mount_point())
119 .collect()
120 }
121
122 pub fn update_nvim_listen_address(&mut self) {
123 if let Ok(nvim_listen_address) = std::env::var("NVIM_LISTEN_ADDRESS") {
124 self.nvim_server = nvim_listen_address;
125 } else if let Ok(nvim_listen_address) = Self::parse_nvim_address_from_ss_output() {
126 self.nvim_server = nvim_listen_address;
127 }
128 }
129
130 fn parse_nvim_address_from_ss_output() -> Result<String> {
131 if !is_in_path(SS) {
132 return Err(anyhow!("{SS} isn't installed"));
133 }
134 if let Ok(output) = execute_and_output(SS, ["-l"]) {
135 let output = String::from_utf8(output.stdout).unwrap_or_default();
136 let content: String = output
137 .split(&['\n', '\t', ' '])
138 .find(|w| w.contains(NVIM))
139 .unwrap_or("")
140 .to_string();
141 if !content.is_empty() {
142 return Ok(content);
143 }
144 }
145 Err(anyhow!("Couldn't get nvim listen address from `ss` output"))
146 }
147
148 pub fn copy_file_remove_head(&mut self) -> Result<()> {
150 if self.copy_file_queue.is_empty() {
151 Err(anyhow!("Copy File Pool is empty"))
152 } else {
153 self.copy_file_queue.remove(0);
154 Ok(())
155 }
156 }
157
158 pub fn copy_next_file_in_queue(
159 &mut self,
160 fm_sender: Arc<Sender<FmEvents>>,
161 width: u16,
162 ) -> Result<()> {
163 let (sources, dest) = self.copy_file_queue[0].clone();
164 let Size { width: _, height } = self.term_size();
165 let in_mem = copy_move(
166 crate::modes::CopyMove::Copy,
167 sources,
168 dest,
169 width,
170 height,
171 fm_sender,
172 )?;
173 self.store_copy_progress(in_mem);
174 Ok(())
175 }
176
177 pub fn store_copy_progress(&mut self, in_mem_progress_bar: InMemoryTerm) {
181 self.in_mem_progress = Some(in_mem_progress_bar);
182 }
183
184 pub fn unset_copy_progress(&mut self) {
186 self.in_mem_progress = None;
187 }
188
189 pub fn disable_display(&mut self) {
192 self.is_disabled = true;
193 }
194
195 pub fn enable_display(&mut self) {
201 if !self.is_disabled() {
202 return;
203 }
204 self.is_disabled = false;
205 self.force_clear();
206 self.clear_before_quit = true;
207 }
208
209 pub fn is_disabled(&self) -> bool {
210 self.is_disabled
211 }
212
213 pub fn open_in_window(&mut self, args: &[&str]) -> Result<()> {
214 self.disable_display();
215 External::open_command_in_window(args)?;
216 self.enable_display();
217 Ok(())
218 }
219
220 fn should_this_file_be_opened_in_neovim(&self, path: &Path) -> bool {
221 matches!(Extension::matcher(extract_extension(path)), Extension::Text)
222 }
223
224 pub fn open_single_file(&mut self, path: &Path) -> Result<()> {
225 if self.inside_neovim && self.should_this_file_be_opened_in_neovim(path) {
226 self.update_nvim_listen_address();
227 open_in_current_neovim(path, &self.nvim_server);
228 Ok(())
229 } else if self.opener.use_term(path) {
230 self.open_single_in_window(path);
231 Ok(())
232 } else {
233 self.opener.open_single(path)
234 }
235 }
236
237 fn open_single_in_window(&mut self, path: &Path) {
238 self.disable_display();
239 self.opener.open_in_window(path);
240 self.enable_display();
241 }
242
243 pub fn open_flagged_files(&mut self, flagged: &Flagged) -> Result<()> {
244 if self.inside_neovim && flagged.should_all_be_opened_in_neovim() {
245 self.open_multiple_in_neovim(flagged.content());
246 Ok(())
247 } else {
248 self.open_multiple_outside(flagged.content())
249 }
250 }
251
252 fn open_multiple_outside(&mut self, paths: &[PathBuf]) -> Result<()> {
253 let openers = self.opener.regroup_per_opener(paths);
254 if Self::all_files_opened_in_terminal(&openers) {
255 self.open_multiple_files_in_window(openers)
256 } else {
257 self.opener.open_multiple(openers)
258 }
259 }
260
261 fn all_files_opened_in_terminal(openers: &HashMap<External, Vec<PathBuf>>) -> bool {
262 openers.len() == 1 && openers.keys().next().expect("Can't be empty").use_term()
263 }
264
265 fn open_multiple_files_in_window(
266 &mut self,
267 openers: HashMap<External, Vec<PathBuf>>,
268 ) -> Result<()> {
269 self.disable_display();
270 self.opener.open_multiple_in_window(openers)?;
271 self.enable_display();
272 Ok(())
273 }
274
275 fn open_multiple_in_neovim(&mut self, paths: &[PathBuf]) {
276 self.update_nvim_listen_address();
277 for path in paths {
278 open_in_current_neovim(path, &self.nvim_server);
279 }
280 }
281
282 pub fn quit(&mut self) {
286 self.must_quit = true
287 }
288
289 pub fn format_copy_progress(&self) -> Option<String> {
290 let Some(copy_progress) = &self.in_mem_progress else {
291 return None;
292 };
293 let progress_bar = copy_progress.contents();
294 let nb_copy_left = self.copy_file_queue.len();
295 if nb_copy_left <= 1 {
296 Some(progress_bar)
297 } else {
298 Some(format!(
299 "{progress_bar} - 1 of {nb}",
300 nb = nb_copy_left
301 ))
302 }
303 }
304}