fm/modes/utils/
leave_menu.rs1use std::str::FromStr;
2
3use anyhow::{bail, Context, Result};
4
5use crate::app::Status;
6use crate::common::{path_to_string, rename_fullpath, string_to_path};
7use crate::config::Bindings;
8use crate::event::{ActionMap, EventAction, FmEvents};
9use crate::modes::{
10 Content, InputCompleted, InputSimple, Leave, MarkAction, Menu, MountAction, Navigate,
11 NodeCreation, PasswordUsage, PickerCaller, TerminalApplications,
12};
13use crate::{log_info, log_line};
14
15pub struct LeaveMenu;
17
18impl LeaveMenu {
19 pub fn leave_menu(status: &mut Status, binds: &Bindings) -> Result<()> {
20 status
21 .menu
22 .input_history
23 .update(status.current_tab().menu_mode, &status.menu.input.string())?;
24 let must_refresh = status.current_tab().menu_mode.must_refresh();
25 let must_reset_mode = status.current_tab().menu_mode.must_reset_mode();
26
27 match status.current_tab().menu_mode {
28 Menu::Nothing => Ok(()),
29 Menu::InputSimple(InputSimple::Rename) => LeaveMenu::rename(status),
30 Menu::InputSimple(InputSimple::Newfile) => LeaveMenu::new_file(status),
31 Menu::InputSimple(InputSimple::Newdir) => LeaveMenu::new_dir(status),
32 Menu::InputSimple(InputSimple::Chmod) => LeaveMenu::chmod(status),
33 Menu::InputSimple(InputSimple::RegexMatch) => LeaveMenu::regex_match(status),
34 Menu::InputSimple(InputSimple::SetNvimAddr) => LeaveMenu::set_nvim_addr(status),
35 Menu::InputSimple(InputSimple::ShellCommand) => LeaveMenu::shell_command(status),
36 Menu::InputSimple(InputSimple::Sort) => LeaveMenu::sort(status),
37 Menu::InputSimple(InputSimple::Filter) => LeaveMenu::filter(status),
38 Menu::InputSimple(InputSimple::Password(action, usage)) => {
39 LeaveMenu::password(status, action, usage)
40 }
41 Menu::InputSimple(InputSimple::CloudNewdir) => {
42 LeaveMenu::cloud_newdir(status)?;
43 return Ok(());
44 }
45 Menu::InputSimple(InputSimple::Remote) => LeaveMenu::remote(status),
46 Menu::Navigate(Navigate::History) => LeaveMenu::history(status),
47 Menu::Navigate(Navigate::Shortcut) => LeaveMenu::shortcut(status),
48 Menu::Navigate(Navigate::Trash) => LeaveMenu::trash(status),
49 Menu::Navigate(Navigate::TuiApplication) => LeaveMenu::tui_application(status),
50 Menu::Navigate(Navigate::CliApplication) => LeaveMenu::cli_info(status),
51 Menu::Navigate(Navigate::Cloud) => {
52 LeaveMenu::cloud_enter(status)?;
53 return Ok(());
54 }
55 Menu::Navigate(Navigate::Marks(MarkAction::New)) => LeaveMenu::marks_update(status),
56 Menu::Navigate(Navigate::Marks(MarkAction::Jump)) => LeaveMenu::marks_jump(status),
57 Menu::Navigate(Navigate::TempMarks(MarkAction::New)) => {
58 LeaveMenu::tempmark_update(status)
59 }
60 Menu::Navigate(Navigate::TempMarks(MarkAction::Jump)) => LeaveMenu::tempmark_jp(status),
61 Menu::Navigate(Navigate::Compress) => LeaveMenu::compress(status),
62 Menu::Navigate(Navigate::Mount) => LeaveMenu::go_to_mount(status),
63 Menu::Navigate(Navigate::Context) => LeaveMenu::context(status, binds),
64 Menu::Navigate(Navigate::Picker) => {
65 LeaveMenu::picker(status)?;
66 return Ok(());
67 }
68 Menu::Navigate(Navigate::Flagged) => LeaveMenu::flagged(status),
69 Menu::InputCompleted(InputCompleted::Exec) => {
70 LeaveMenu::exec(status)?;
71 return Ok(());
72 }
73 Menu::InputCompleted(InputCompleted::Search) => Ok(()),
74 Menu::InputCompleted(InputCompleted::Cd) => LeaveMenu::cd(status),
75 Menu::InputCompleted(InputCompleted::Action) => LeaveMenu::action(status),
76 Menu::NeedConfirmation(_) => Ok(()),
78 }?;
79
80 status.menu.input.reset();
81 if must_reset_mode {
82 status.reset_menu_mode()?;
83 }
84 if must_refresh {
85 status.refresh_status()?;
86 }
87 Ok(())
88 }
89
90 pub fn trash(status: &mut Status) -> Result<()> {
93 if status.focus.is_file() {
94 return Ok(());
95 }
96 let _ = status.menu.trash.restore();
97 status.reset_menu_mode()?;
98 status.current_tab_mut().refresh_view()?;
99 status.update_second_pane_for_preview()
100 }
101
102 fn marks_jump(status: &mut Status) -> Result<()> {
104 if let Some((_, path)) = &status.menu.marks.selected() {
105 let len = status.current_tab().directory.content.len();
106 status.tabs[status.index].cd(path)?;
107 status.current_tab_mut().window.reset(len);
108 status.menu.input.reset();
109 }
110 status.update_second_pane_for_preview()
111 }
112
113 fn marks_update(status: &mut Status) -> Result<()> {
117 if let Some((ch, _)) = status.menu.marks.selected() {
118 let len = status.current_tab().directory.content.len();
119 let new_path = &status.tabs[status.index].directory.path;
120 log_line!("Saved mark {ch} -> {p}", p = new_path.display());
121 status.menu.marks.new_mark(*ch, new_path)?;
122 status.current_tab_mut().window.reset(len);
123 status.menu.input.reset();
124 }
125 Ok(())
126 }
127
128 fn tempmark_update(status: &mut Status) -> Result<()> {
132 let index = status.menu.temp_marks.index;
133 let len = status.current_tab().directory.content.len();
134 let new_path = &status.tabs[status.index].directory_of_selected()?;
135 status
136 .menu
137 .temp_marks
138 .set_mark(index as _, new_path.to_path_buf());
139 log_line!("Saved temp mark {index} -> {p}", p = new_path.display());
140 status.current_tab_mut().window.reset(len);
141 status.menu.input.reset();
142 Ok(())
143 }
144
145 fn tempmark_jp(status: &mut Status) -> Result<()> {
147 let Some(opt_path) = &status.menu.temp_marks.selected() else {
148 log_info!("no selected temp mark");
149 return Ok(());
150 };
151 let Some(path) = opt_path else {
152 return Ok(());
153 };
154 let len = status.current_tab().directory.content.len();
155 status.tabs[status.index].cd(path)?;
156 status.current_tab_mut().window.reset(len);
157 status.menu.input.reset();
158
159 status.update_second_pane_for_preview()
160 }
161
162 fn tui_application(status: &mut Status) -> Result<()> {
165 status.internal_settings.disable_display();
166 status.menu.tui_applications.execute(status)?;
167 status.internal_settings.enable_display();
168 Ok(())
169 }
170
171 fn cli_info(status: &mut Status) -> Result<()> {
172 let (output, command) = status.menu.cli_applications.execute(status)?;
173 log_info!("cli info: command {command}, output\n{output}");
174 status.preview_command_output(output, command);
175 Ok(())
176 }
177
178 fn cloud_enter(status: &mut Status) -> Result<()> {
179 status.cloud_enter_file_or_dir()
180 }
181
182 fn cloud_newdir(status: &mut Status) -> Result<()> {
183 status.cloud_create_newdir(status.menu.input.string())?;
184 status.reset_menu_mode()?;
185 status.cloud_open()
186 }
187
188 fn chmod(status: &mut Status) -> Result<()> {
194 status.chmod()
195 }
196
197 fn set_nvim_addr(status: &mut Status) -> Result<()> {
198 status.internal_settings.nvim_server = status.menu.input.string();
199 status.reset_menu_mode()?;
200 Ok(())
201 }
202
203 fn regex_match(status: &mut Status) -> Result<()> {
205 status.flag_from_regex()?;
206 status.menu.input.reset();
207 Ok(())
208 }
209
210 fn shell_command(status: &mut Status) -> Result<()> {
214 status.execute_shell_command_from_input()?;
215 Ok(())
216 }
217
218 fn rename(status: &mut Status) -> Result<()> {
224 if status.menu.input.is_empty() {
225 log_line!("Can't rename: new name is empty");
226 log_info!("Can't rename: new name is empty");
227 return Ok(());
228 }
229 let new_path = status.menu.input.string();
230 let old_path = status.current_tab().current_file()?.path;
231 match rename_fullpath(&old_path, &new_path) {
232 Ok(()) => {
233 status.rename_marks(&old_path, &new_path)?;
234 status.current_tab_mut().refresh_view()?;
235 status.current_tab_mut().cd_to_file(&new_path)?;
236 }
237 Err(error) => {
238 log_info!(
239 "Error renaming {old_path} to {new_path}. Error: {error}",
240 old_path = old_path.display()
241 );
242 log_line!(
243 "Error renaming {old_path} to {new_path}. Error: {error}",
244 old_path = old_path.display()
245 );
246 }
247 }
248 Ok(())
249 }
250
251 fn new_file(status: &mut Status) -> Result<()> {
254 match NodeCreation::Newfile.create(status) {
255 Ok(path) => {
256 status.current_tab_mut().go_to_file(&path);
257 status.menu.flagged.push(path);
258 status.refresh_tabs()?;
259 }
260 Err(error) => log_info!("Error creating file. Error: {error}",),
261 }
262 Ok(())
263 }
264
265 fn new_dir(status: &mut Status) -> Result<()> {
270 match NodeCreation::Newdir.create(status) {
271 Ok(path) => {
272 status.refresh_tabs()?;
273 status.current_tab_mut().go_to_file(&path);
274 status.menu.flagged.push(path);
275 }
276 Err(error) => log_info!("Error creating directory. Error: {error}",),
277 }
278 Ok(())
279 }
280
281 fn exec(status: &mut Status) -> Result<()> {
286 if status.current_tab().directory.content.is_empty() {
287 bail!("exec: empty directory")
288 }
289 let exec_command = status.menu.input.string();
290 if status.execute_shell_command(
291 exec_command,
292 Some(status.menu.flagged.as_strings()),
293 false,
294 )? {
295 status.menu.completion.reset();
296 status.menu.input.reset();
297 }
298 Ok(())
299 }
300
301 fn cd(status: &mut Status) -> Result<()> {
306 if status.menu.completion.is_empty() {
307 return Ok(());
308 }
309 let completed = status.menu.completion.current_proposition();
310 let path = string_to_path(completed)?;
311 status.thumbnail_queue_clear();
312 status.menu.input.reset();
313 status.current_tab_mut().cd_to_file(&path)?;
314 let len = status.current_tab().directory.content.len();
315 status.current_tab_mut().window.reset(len);
316 status.update_second_pane_for_preview()
317 }
318
319 fn shortcut(status: &mut Status) -> Result<()> {
322 let path = status
323 .menu
324 .shortcut
325 .selected()
326 .context("exec shortcut: empty shortcuts")?;
327 status.tabs[status.index].cd(path)?;
328 status.current_tab_mut().refresh_view()?;
329 status.update_second_pane_for_preview()
330 }
331
332 fn sort(status: &mut Status) -> Result<()> {
333 status.current_tab_mut().set_sortkind_per_mode();
334 status.update_second_pane_for_preview()?;
335 status.focus = status.focus.to_parent();
336 Ok(())
337 }
338
339 fn history(status: &mut Status) -> Result<()> {
342 status.current_tab_mut().history_cd_to_last()?;
343 status.update_second_pane_for_preview()
344 }
345
346 fn password(
348 status: &mut Status,
349 action: Option<MountAction>,
350 usage: PasswordUsage,
351 ) -> Result<()> {
352 status.execute_password_command(action, usage)
353 }
354
355 fn compress(status: &mut Status) -> Result<()> {
361 status.compress()
362 }
363
364 fn context(status: &mut Status, binds: &Bindings) -> Result<()> {
366 let command = status.menu.context.matcher().to_owned();
367 EventAction::reset_mode(status)?;
368 command.matcher(status, binds)
369 }
370
371 fn action(status: &mut Status) -> Result<()> {
375 let action_str = status.menu.completion.current_proposition();
376 let Ok(action) = ActionMap::from_str(action_str) else {
377 return Ok(());
378 };
379
380 status.reset_menu_mode()?;
381 status.focus = status.focus.to_parent();
382 status.fm_sender.send(FmEvents::Action(action))?;
383 Ok(())
384 }
385
386 fn filter(status: &mut Status) -> Result<()> {
389 status.filter()?;
390 status.menu.input.reset();
391 Ok(())
392 }
393
394 fn remote(status: &mut Status) -> Result<()> {
399 let current_path = &path_to_string(&status.current_tab().directory_of_selected()?);
400 status.menu.mount_remote(current_path);
401 Ok(())
402 }
403
404 fn go_to_mount(status: &mut Status) -> Result<()> {
406 match status.current_tab().menu_mode {
407 Menu::Navigate(Navigate::Mount) => status.go_to_normal_drive(),
408 _ => Ok(()),
409 }
410 }
411
412 fn picker(status: &mut Status) -> Result<()> {
413 let Some(caller) = &status.menu.picker.caller else {
414 return Ok(());
415 };
416 match caller {
417 PickerCaller::Cloud => status.cloud_load_config(),
418 PickerCaller::Menu(menu) => EventAction::reenter_menu_from_picker(status, *menu),
419 PickerCaller::Unknown => Ok(()),
420 }
421 }
422
423 fn flagged(status: &mut Status) -> Result<()> {
424 status.jump_flagged()
425 }
426}