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)) => LeaveMenu::tempmark_upd(status),
58 Menu::Navigate(Navigate::TempMarks(MarkAction::Jump)) => LeaveMenu::tempmark_jp(status),
59 Menu::Navigate(Navigate::Compress) => LeaveMenu::compress(status),
60 Menu::Navigate(Navigate::Mount) => LeaveMenu::go_to_mount(status),
61 Menu::Navigate(Navigate::Context) => LeaveMenu::context(status, binds),
62 Menu::Navigate(Navigate::Picker) => {
63 LeaveMenu::picker(status)?;
64 return Ok(());
65 }
66 Menu::Navigate(Navigate::Flagged) => LeaveMenu::flagged(status),
67 Menu::InputCompleted(InputCompleted::Exec) => {
68 LeaveMenu::exec(status)?;
69 return Ok(());
70 }
71 Menu::InputCompleted(InputCompleted::Search) => Ok(()),
72 Menu::InputCompleted(InputCompleted::Cd) => LeaveMenu::cd(status),
73 Menu::InputCompleted(InputCompleted::Action) => LeaveMenu::action(status),
74 Menu::NeedConfirmation(_) => Ok(()),
76 }?;
77
78 status.menu.input.reset();
79 if must_reset_mode {
80 status.reset_menu_mode()?;
81 }
82 if must_refresh {
83 status.refresh_status()?;
84 }
85 Ok(())
86 }
87
88 pub fn trash(status: &mut Status) -> Result<()> {
91 if status.focus.is_file() {
92 return Ok(());
93 }
94 let _ = status.menu.trash.restore();
95 status.reset_menu_mode()?;
96 status.current_tab_mut().refresh_view()?;
97 status.update_second_pane_for_preview()
98 }
99
100 fn marks_jump(status: &mut Status) -> Result<()> {
102 if let Some((_, path)) = &status.menu.marks.selected() {
103 let len = status.current_tab().directory.content.len();
104 status.tabs[status.index].cd(path)?;
105 status.current_tab_mut().window.reset(len);
106 status.menu.input.reset();
107 }
108 status.update_second_pane_for_preview()
109 }
110
111 fn marks_update(status: &mut Status) -> Result<()> {
115 if let Some((ch, _)) = status.menu.marks.selected() {
116 let len = status.current_tab().directory.content.len();
117 let new_path = &status.tabs[status.index].directory.path;
118 log_line!("Saved mark {ch} -> {p}", p = new_path.display());
119 status.menu.marks.new_mark(*ch, new_path)?;
120 status.current_tab_mut().window.reset(len);
121 status.menu.input.reset();
122 }
123 Ok(())
124 }
125
126 fn tempmark_upd(status: &mut Status) -> Result<()> {
130 let index = status.menu.temp_marks.index;
131 let len = status.current_tab().directory.content.len();
132 let new_path = &status.tabs[status.index].directory.path;
133 status
134 .menu
135 .temp_marks
136 .set_mark(index as _, new_path.to_path_buf());
137 log_line!("Saved temp mark {index} -> {p}", p = new_path.display());
138 status.current_tab_mut().window.reset(len);
139 status.menu.input.reset();
140 Ok(())
141 }
142
143 fn tempmark_jp(status: &mut Status) -> Result<()> {
145 let Some(opt_path) = &status.menu.temp_marks.selected() else {
146 log_info!("no selected temp mark");
147 return Ok(());
148 };
149 let Some(path) = opt_path else {
150 return Ok(());
151 };
152 let len = status.current_tab().directory.content.len();
153 status.tabs[status.index].cd(path)?;
154 status.current_tab_mut().window.reset(len);
155 status.menu.input.reset();
156
157 status.update_second_pane_for_preview()
158 }
159
160 fn tui_application(status: &mut Status) -> Result<()> {
163 status.internal_settings.disable_display();
164 status.menu.tui_applications.execute(status)?;
165 status.internal_settings.enable_display();
166 Ok(())
167 }
168
169 fn cli_info(status: &mut Status) -> Result<()> {
170 let (output, command) = status.menu.cli_applications.execute(status)?;
171 log_info!("cli info: command {command}, output\n{output}");
172 status.preview_command_output(output, command);
173 Ok(())
174 }
175
176 fn cloud_enter(status: &mut Status) -> Result<()> {
177 status.cloud_enter_file_or_dir()
178 }
179
180 fn cloud_newdir(status: &mut Status) -> Result<()> {
181 status.cloud_create_newdir(status.menu.input.string())?;
182 status.reset_menu_mode()?;
183 status.cloud_open()
184 }
185
186 fn chmod(status: &mut Status) -> Result<()> {
192 status.chmod()
193 }
194
195 fn set_nvim_addr(status: &mut Status) -> Result<()> {
196 status.internal_settings.nvim_server = status.menu.input.string();
197 status.reset_menu_mode()?;
198 Ok(())
199 }
200
201 fn regex_match(status: &mut Status) -> Result<()> {
203 status.flag_from_regex()?;
204 status.menu.input.reset();
205 Ok(())
206 }
207
208 fn shell_command(status: &mut Status) -> Result<()> {
212 status.execute_shell_command_from_input()?;
213 Ok(())
214 }
215
216 fn rename(status: &mut Status) -> Result<()> {
222 if status.menu.input.is_empty() {
223 log_line!("Can't rename: new name is empty");
224 log_info!("Can't rename: new name is empty");
225 return Ok(());
226 }
227 let new_path = status.menu.input.string();
228 let old_path = status.current_tab().current_file()?.path;
229 match rename_fullpath(&old_path, &new_path) {
230 Ok(()) => {
231 status.current_tab_mut().refresh_view()?;
232 status.current_tab_mut().cd_to_file(&new_path)?;
233 }
234 Err(error) => {
235 log_info!(
236 "Error renaming {old_path} to {new_path}. Error: {error}",
237 old_path = old_path.display()
238 );
239 log_line!(
240 "Error renaming {old_path} to {new_path}. Error: {error}",
241 old_path = old_path.display()
242 );
243 }
244 }
245 Ok(())
246 }
247
248 fn new_file(status: &mut Status) -> Result<()> {
251 match NodeCreation::Newfile.create(status) {
252 Ok(path) => {
253 status.current_tab_mut().go_to_file(&path);
254 status.menu.flagged.push(path);
255 status.refresh_tabs()?;
256 }
257 Err(error) => log_info!("Error creating file. Error: {error}",),
258 }
259 Ok(())
260 }
261
262 fn new_dir(status: &mut Status) -> Result<()> {
267 match NodeCreation::Newdir.create(status) {
268 Ok(path) => {
269 status.refresh_tabs()?;
270 status.current_tab_mut().go_to_file(&path);
271 status.menu.flagged.push(path);
272 }
273 Err(error) => log_info!("Error creating directory. Error: {error}",),
274 }
275 Ok(())
276 }
277
278 fn exec(status: &mut Status) -> Result<()> {
283 if status.current_tab().directory.content.is_empty() {
284 bail!("exec: empty directory")
285 }
286 let exec_command = status.menu.input.string();
287 if status.execute_shell_command(
288 exec_command,
289 Some(status.menu.flagged.as_strings()),
290 false,
291 )? {
292 status.menu.completion.reset();
293 status.menu.input.reset();
294 }
295 Ok(())
296 }
297
298 fn cd(status: &mut Status) -> Result<()> {
303 if status.menu.completion.is_empty() {
304 return Ok(());
305 }
306 let completed = status.menu.completion.current_proposition();
307 let path = string_to_path(completed)?;
308 status.thumbnail_queue_clear();
309 status.menu.input.reset();
310 status.current_tab_mut().cd_to_file(&path)?;
311 let len = status.current_tab().directory.content.len();
312 status.current_tab_mut().window.reset(len);
313 status.update_second_pane_for_preview()
314 }
315
316 fn shortcut(status: &mut Status) -> Result<()> {
319 let path = status
320 .menu
321 .shortcut
322 .selected()
323 .context("exec shortcut: empty shortcuts")?;
324 status.tabs[status.index].cd(path)?;
325 status.current_tab_mut().refresh_view()?;
326 status.update_second_pane_for_preview()
327 }
328
329 fn sort(status: &mut Status) -> Result<()> {
330 status.current_tab_mut().set_sortkind_per_mode();
331 status.update_second_pane_for_preview()?;
332 status.focus = status.focus.to_parent();
333 Ok(())
334 }
335
336 fn history(status: &mut Status) -> Result<()> {
339 status.current_tab_mut().history_cd_to_last()?;
340 status.update_second_pane_for_preview()
341 }
342
343 fn password(
345 status: &mut Status,
346 action: Option<MountAction>,
347 usage: PasswordUsage,
348 ) -> Result<()> {
349 status.execute_password_command(action, usage)
350 }
351
352 fn compress(status: &mut Status) -> Result<()> {
358 status.compress()
359 }
360
361 fn context(status: &mut Status, binds: &Bindings) -> Result<()> {
363 let command = status.menu.context.matcher().to_owned();
364 EventAction::reset_mode(status)?;
365 command.matcher(status, binds)
366 }
367
368 fn action(status: &mut Status) -> Result<()> {
372 let action_str = status.menu.completion.current_proposition();
373 let Ok(action) = ActionMap::from_str(action_str) else {
374 return Ok(());
375 };
376
377 status.reset_menu_mode()?;
378 status.focus = status.focus.to_parent();
379 status.fm_sender.send(FmEvents::Action(action))?;
380 Ok(())
381 }
382
383 fn filter(status: &mut Status) -> Result<()> {
386 status.filter()?;
387 status.menu.input.reset();
388 Ok(())
389 }
390
391 fn remote(status: &mut Status) -> Result<()> {
396 let current_path = &path_to_string(&status.current_tab().directory_of_selected()?);
397 status.menu.mount_remote(current_path);
398 Ok(())
399 }
400
401 fn go_to_mount(status: &mut Status) -> Result<()> {
403 match status.current_tab().menu_mode {
404 Menu::Navigate(Navigate::Mount) => status.go_to_normal_drive(),
405 _ => Ok(()),
406 }
407 }
408
409 fn picker(status: &mut Status) -> Result<()> {
410 let Some(caller) = &status.menu.picker.caller else {
411 return Ok(());
412 };
413 match caller {
414 PickerCaller::Cloud => status.cloud_load_config(),
415 PickerCaller::Menu(menu) => EventAction::reenter_menu_from_picker(status, *menu),
416 PickerCaller::Unknown => Ok(()),
417 }
418 }
419
420 fn flagged(status: &mut Status) -> Result<()> {
421 status.jump_flagged()
422 }
423}