fm/app/
internal_settings.rs1use 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::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(true);
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 bail!("{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 bail!("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 self.copy_file_queue.remove(0);
152 }
153 Ok(())
154 }
155
156 pub fn copy_next_file_in_queue(
157 &mut self,
158 fm_sender: Arc<Sender<FmEvents>>,
159 width: u16,
160 ) -> Result<()> {
161 let (sources, dest) = self.copy_file_queue[0].clone();
162 let Size { width: _, height } = self.term_size();
163 let in_mem = copy_move(
164 crate::modes::CopyMove::Copy,
165 sources,
166 dest,
167 width,
168 height,
169 fm_sender,
170 )?;
171 self.store_copy_progress(in_mem);
172 Ok(())
173 }
174
175 pub fn store_copy_progress(&mut self, in_mem_progress_bar: InMemoryTerm) {
179 self.in_mem_progress = Some(in_mem_progress_bar);
180 }
181
182 pub fn unset_copy_progress(&mut self) {
184 self.in_mem_progress = None;
185 }
186
187 pub fn disable_display(&mut self) {
190 self.is_disabled = true;
191 }
192
193 pub fn enable_display(&mut self) {
199 if !self.is_disabled() {
200 return;
201 }
202 self.is_disabled = false;
203 self.force_clear();
204 self.clear_before_quit = true;
205 }
206
207 pub fn is_disabled(&self) -> bool {
208 self.is_disabled
209 }
210
211 pub fn open_in_window(&mut self, args: &[&str]) -> Result<()> {
212 self.disable_display();
213 External::open_command_in_window(args)?;
214 self.enable_display();
215 Ok(())
216 }
217
218 fn should_this_file_be_opened_in_neovim(&self, path: &Path) -> bool {
219 matches!(Extension::matcher(extract_extension(path)), Extension::Text)
220 }
221
222 pub fn open_single_file(&mut self, path: &Path) -> Result<()> {
223 if self.inside_neovim && self.should_this_file_be_opened_in_neovim(path) {
224 self.update_nvim_listen_address();
225 open_in_current_neovim(path, &self.nvim_server);
226 Ok(())
227 } else if self.opener.use_term(path) {
228 self.open_single_in_window(path);
229 Ok(())
230 } else {
231 self.opener.open_single(path)
232 }
233 }
234
235 fn open_single_in_window(&mut self, path: &Path) {
236 self.disable_display();
237 self.opener.open_in_window(path);
238 self.enable_display();
239 }
240
241 pub fn open_flagged_files(&mut self, flagged: &Flagged) -> Result<()> {
242 if self.inside_neovim && flagged.should_all_be_opened_in_neovim() {
243 self.open_multiple_in_neovim(flagged.content());
244 Ok(())
245 } else {
246 self.open_multiple_outside(flagged.content())
247 }
248 }
249
250 fn open_multiple_outside(&mut self, paths: &[PathBuf]) -> Result<()> {
251 let openers = self.opener.regroup_per_opener(paths);
252 if Self::all_files_opened_in_terminal(&openers) {
253 self.open_multiple_files_in_window(openers)
254 } else {
255 self.opener.open_multiple(openers)
256 }
257 }
258
259 fn all_files_opened_in_terminal(openers: &HashMap<External, Vec<PathBuf>>) -> bool {
260 openers.len() == 1 && openers.keys().next().expect("Can't be empty").use_term()
261 }
262
263 fn open_multiple_files_in_window(
264 &mut self,
265 openers: HashMap<External, Vec<PathBuf>>,
266 ) -> Result<()> {
267 self.disable_display();
268 self.opener.open_multiple_in_window(openers)?;
269 self.enable_display();
270 Ok(())
271 }
272
273 fn open_multiple_in_neovim(&mut self, paths: &[PathBuf]) {
274 self.update_nvim_listen_address();
275 for path in paths {
276 open_in_current_neovim(path, &self.nvim_server);
277 }
278 }
279
280 pub fn quit(&mut self) {
284 self.must_quit = true
285 }
286
287 pub fn format_copy_progress(&self) -> Option<String> {
288 let Some(copy_progress) = &self.in_mem_progress else {
289 return None;
290 };
291 let progress_bar = copy_progress.contents();
292 let nb_copy_left = self.copy_file_queue.len();
293 if nb_copy_left <= 1 {
294 Some(progress_bar)
295 } else {
296 Some(format!(
297 "{progress_bar} - 1 of {nb}",
298 nb = nb_copy_left
299 ))
300 }
301 }
302}