1use std::fmt;
2
3use anyhow::Result;
4
5use crate::app::Status;
6use crate::common::{
7 UtfWidth, CHMOD_LINES, CLOUD_NEWDIR_LINES, FILTER_LINES, NEWDIR_LINES, NEWFILE_LINES,
8 NVIM_ADDRESS_LINES, PASSWORD_LINES_DEVICE, PASSWORD_LINES_SUDO, REGEX_LINES, REMOTE_LINES,
9 RENAME_LINES, SHELL_LINES, SORT_LINES,
10};
11use crate::event::EventAction;
12use crate::modes::InputCompleted;
13use crate::modes::MountAction;
14use crate::modes::{PasswordKind, PasswordUsage};
15
16#[derive(Clone, Copy, Debug, Eq, PartialEq)]
20pub enum MarkAction {
21 Jump,
23 New,
25}
26
27#[derive(Clone, Copy, Debug, Eq, PartialEq)]
30pub enum NeedConfirmation {
31 Copy,
33 Delete,
35 Move,
37 EmptyTrash,
39 BulkAction,
41 DeleteCloud,
43}
44
45impl NeedConfirmation {
46 #[must_use]
49 pub fn confirmation_string(&self, destination: &str) -> String {
50 match *self {
51 Self::Copy => {
52 format!("Files will be copied to {destination}")
53 }
54 Self::Delete | Self::EmptyTrash => "Files will be deleted permanently".to_owned(),
55 Self::Move => {
56 format!("Files will be moved to {destination}")
57 }
58 Self::BulkAction => "Those files will be renamed or created :".to_owned(),
59 Self::DeleteCloud => "Remote Files will be deleted permanently".to_owned(),
60 }
61 }
62
63 pub fn use_flagged_files(&self) -> bool {
64 matches!(self, Self::Copy | Self::Move | Self::Delete)
65 }
66}
67
68impl CursorOffset for NeedConfirmation {
69 fn cursor_offset(&self) -> u16 {
73 self.to_string().utf_width_u16() + 9
74 }
75}
76
77impl Leave for NeedConfirmation {
78 fn must_refresh(&self) -> bool {
79 true
80 }
81
82 fn must_reset_mode(&self) -> bool {
83 true
84 }
85}
86
87impl std::fmt::Display for NeedConfirmation {
88 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
89 match *self {
90 Self::Delete => write!(f, "Delete files :"),
91 Self::DeleteCloud => write!(f, "Delete files :"),
92 Self::Move => write!(f, "Move files here :"),
93 Self::Copy => write!(f, "Copy files here :"),
94 Self::EmptyTrash => write!(f, "Empty the trash ?"),
95 Self::BulkAction => write!(f, "Bulk :"),
96 }
97 }
98}
99
100#[derive(Clone, Copy, PartialEq, Eq)]
106pub enum InputSimple {
107 Rename,
109 Chmod,
111 Newfile,
113 Newdir,
115 RegexMatch,
117 Sort,
119 Filter,
121 SetNvimAddr,
123 Password(Option<MountAction>, PasswordUsage),
125 ShellCommand,
127 Remote,
129 CloudNewdir,
131}
132
133impl fmt::Display for InputSimple {
134 fn fmt(&self, f: &mut std::fmt::Formatter) -> fmt::Result {
135 match *self {
136 Self::Rename => write!(f, "Rename: "),
137 Self::Chmod => write!(f, "Chmod: "),
138 Self::Newfile => write!(f, "Newfile: "),
139 Self::Newdir => write!(f, "Newdir: "),
140 Self::RegexMatch => write!(f, "Regex: "),
141 Self::SetNvimAddr => write!(f, "Neovim: "),
142 Self::CloudNewdir => write!(f, "Newdir: "),
143 Self::ShellCommand => write!(f, "Shell: "),
144 Self::Sort => {
145 write!(f, "Sort: ")
146 }
147 Self::Filter => write!(f, "Filter: "),
148 Self::Password(_, PasswordUsage::CRYPTSETUP(password_kind)) => {
149 write!(f, "{password_kind}")
150 }
151 Self::Password(_, _) => write!(f, " sudo: "),
152 Self::Remote => write!(f, "Remote: "),
153 }
154 }
155}
156
157impl InputSimple {
158 const EDIT_BOX_OFFSET: u16 = 11;
159 const SORT_CURSOR_OFFSET: u16 = 8;
160 const PASSWORD_CURSOR_OFFSET: u16 = 9;
161
162 #[must_use]
165 pub const fn lines(&self) -> &'static [&'static str] {
166 match *self {
167 Self::Chmod => &CHMOD_LINES,
168 Self::Filter => &FILTER_LINES,
169 Self::Newdir => &NEWDIR_LINES,
170 Self::Newfile => &NEWFILE_LINES,
171 Self::Password(_, PasswordUsage::CRYPTSETUP(PasswordKind::SUDO)) => {
172 &PASSWORD_LINES_SUDO
173 }
174 Self::Password(_, PasswordUsage::CRYPTSETUP(PasswordKind::CRYPTSETUP)) => {
175 &PASSWORD_LINES_DEVICE
176 }
177 Self::Password(_, _) => &PASSWORD_LINES_SUDO,
178 Self::RegexMatch => ®EX_LINES,
179 Self::Rename => &RENAME_LINES,
180 Self::SetNvimAddr => &NVIM_ADDRESS_LINES,
181 Self::ShellCommand => &SHELL_LINES,
182 Self::Sort => &SORT_LINES,
183 Self::Remote => &REMOTE_LINES,
184 Self::CloudNewdir => &CLOUD_NEWDIR_LINES,
185 }
186 }
187}
188
189pub trait CursorOffset {
191 fn cursor_offset(&self) -> u16;
192}
193
194impl CursorOffset for InputSimple {
195 fn cursor_offset(&self) -> u16 {
196 match *self {
197 Self::Sort => Self::SORT_CURSOR_OFFSET,
198 Self::Password(_, _) => Self::PASSWORD_CURSOR_OFFSET,
199 _ => Self::EDIT_BOX_OFFSET,
200 }
201 }
202}
203
204impl Leave for InputSimple {
205 fn must_refresh(&self) -> bool {
206 !matches!(
207 self,
208 Self::ShellCommand | Self::Filter | Self::Password(_, _) | Self::Sort
209 )
210 }
211
212 fn must_reset_mode(&self) -> bool {
213 !matches!(self, Self::ShellCommand | Self::Password(_, _))
214 }
215}
216
217#[derive(Clone, Copy, Debug, Eq, PartialEq)]
221pub enum Navigate {
222 History,
224 Shortcut,
226 Trash,
228 Marks(MarkAction),
230 TempMarks(MarkAction),
232 Mount,
234 Compress,
236 TuiApplication,
238 CliApplication,
240 Context,
242 Cloud,
244 Picker,
246 Flagged,
248}
249
250impl fmt::Display for Navigate {
251 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
252 match *self {
253 Self::Marks(_) => write!(f, "Marks jump:"),
254 Self::TempMarks(_) => write!(f, "Temp marks jump:"),
255 Self::History => write!(f, "History :"),
256 Self::Shortcut => write!(f, "Shortcut :"),
257 Self::Trash => write!(f, "Trash :"),
258 Self::TuiApplication => {
259 write!(f, "Start a new shell running a command:")
260 }
261 Self::Compress => write!(f, "Compress :"),
262 Self::Mount => write!(f, "Mount :"),
263 Self::CliApplication => write!(f, "Display infos :"),
264 Self::Context => write!(f, "Context"),
265 Self::Cloud => write!(f, "Cloud"),
266 Self::Picker => write!(f, "Picker"),
267 Self::Flagged => write!(f, "Flagged"),
268 }
269 }
270}
271
272impl CursorOffset for Navigate {
273 #[inline]
274 fn cursor_offset(&self) -> u16 {
275 0
276 }
277}
278
279impl Leave for Navigate {
280 fn must_refresh(&self) -> bool {
281 !matches!(self, Self::CliApplication | Self::Context)
282 }
283
284 fn must_reset_mode(&self) -> bool {
285 !matches!(self, Self::CliApplication | Self::Context)
286 }
287}
288
289impl Navigate {
290 pub fn simple_draw_menu(&self) -> bool {
292 matches!(
293 self,
294 Self::Compress
295 | Self::Shortcut
296 | Self::TuiApplication
297 | Self::CliApplication
298 | Self::Marks(_)
299 | Self::Mount
300 )
301 }
302}
303
304#[derive(Clone, Copy, Eq, PartialEq)]
307pub enum Menu {
308 InputCompleted(InputCompleted),
315 InputSimple(InputSimple),
320 Navigate(Navigate),
322 NeedConfirmation(NeedConfirmation),
325 Nothing,
327}
328
329impl fmt::Display for Menu {
330 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
331 match *self {
332 Self::InputCompleted(input_completed) => input_completed.fmt(f),
333 Self::InputSimple(input_simple) => input_simple.fmt(f),
334 Self::Navigate(navigate) => navigate.fmt(f),
335 Self::NeedConfirmation(need_confirmation) => need_confirmation.fmt(f),
336 Self::Nothing => write!(f, ""),
337 }
338 }
339}
340
341impl Menu {
342 pub fn show_cursor(&self) -> bool {
344 self.cursor_offset() != 0
345 }
346
347 pub fn binds_per_mode(&self) -> &'static str {
348 match self {
349 Self::InputCompleted(_) => "Tab: completion. shift+⬆️, shift+⬇️: previous entries, shift+⬅️: erase line. Enter: validate",
350 Self::InputSimple(InputSimple::Filter) => "Enter reset the filters",
351 Self::InputSimple(InputSimple::Sort ) => "Enter reset the sort",
352 Self::InputSimple(_) => "shift+⬆️, shift+⬇️: previous entries, shift+⬅️: erase line. Enter: validate",
353 Self::Navigate(Navigate::Marks(MarkAction::Jump)) => "Type the mark letter to jump there. up, down to navigate, ENTER to select an element",
354 Self::Navigate(Navigate::Marks(MarkAction::New)) => "Type the mark set a mark here. up, down to navigate, ENTER to select an element",
355 Self::Navigate(Navigate::TempMarks(MarkAction::New)) => "Type the mark set a mark here. up, down to navigate, ENTER to select an element",
356 Self::Navigate(Navigate::Cloud) => "l: leave drive, arrows: navigation, Enter: enter dir / download file, d: new dir, x: delete selected, u: upload local file",
357 Self::Navigate(Navigate::Flagged) => "Up, Down: navigate, Enter / j: jump to this file, x: remove from flagged, u: clear",
358 Self::Navigate(Navigate::Trash) => "Up, Down: navigate.",
359 Self::Navigate(Navigate::Mount) => "m: mount, u: umount, e: eject (removable), g/ENTER: go to",
360 Self::Navigate(_) => "up, down to navigate, Enter to select an element",
361 Self::NeedConfirmation(_) => "",
362 _ => "",
363 }
364 }
365
366 pub fn is_nothing(&self) -> bool {
368 matches!(self, Self::Nothing)
369 }
370
371 pub fn is_navigate(&self) -> bool {
372 matches!(self, Self::Navigate(_))
373 }
374
375 pub fn is_input(&self) -> bool {
377 matches!(self, Self::InputCompleted(_) | Self::InputSimple(_))
378 }
379
380 pub fn is_complete(&self) -> bool {
381 matches!(self, Self::InputCompleted(_))
382 }
383
384 pub fn is_picker(&self) -> bool {
385 matches!(self, Self::Navigate(Navigate::Picker))
386 }
387
388 pub fn name_for_picker(&self) -> Option<String> {
390 self.to_string().split(':').next().map(|s| s.to_string())
391 }
392}
393
394impl CursorOffset for Menu {
395 #[inline]
398 fn cursor_offset(&self) -> u16 {
399 match self {
400 Self::InputCompleted(input_completed) => input_completed.cursor_offset(),
401 Self::InputSimple(input_simple) => input_simple.cursor_offset(),
402 Self::Navigate(navigate) => navigate.cursor_offset(),
403 Self::NeedConfirmation(confirmed_action) => confirmed_action.cursor_offset(),
404 Self::Nothing => 0,
405 }
406 }
407}
408
409impl Leave for Menu {
410 fn must_refresh(&self) -> bool {
411 match self {
412 Self::InputCompleted(input_completed) => input_completed.must_refresh(),
413 Self::InputSimple(input_simple) => input_simple.must_refresh(),
414 Self::Navigate(navigate) => navigate.must_refresh(),
415 Self::NeedConfirmation(need_confirmation) => need_confirmation.must_refresh(),
416 Self::Nothing => true,
417 }
418 }
419
420 fn must_reset_mode(&self) -> bool {
421 match self {
422 Self::InputCompleted(input_completed) => input_completed.must_reset_mode(),
423 Self::InputSimple(input_simple) => input_simple.must_reset_mode(),
424 Self::Navigate(navigate) => navigate.must_reset_mode(),
425 Self::NeedConfirmation(need_confirmation) => need_confirmation.must_reset_mode(),
426 Self::Nothing => true,
427 }
428 }
429}
430
431pub trait Leave {
435 fn must_refresh(&self) -> bool;
437 fn must_reset_mode(&self) -> bool;
439}
440
441pub trait ReEnterMenu {
442 fn reenter(&self, status: &mut Status) -> Result<()>;
443}
444
445impl ReEnterMenu for Menu {
446 #[rustfmt::skip]
447 fn reenter(&self, status: &mut Status) -> Result<()> {
448 match self {
449 Self::InputCompleted(InputCompleted::Cd) => EventAction::cd(status),
450 Self::InputCompleted(InputCompleted::Search) => EventAction::search(status),
451 Self::InputCompleted(InputCompleted::Exec) => EventAction::exec(status),
452 Self::InputCompleted(InputCompleted::Action) => EventAction::action(status),
453 Self::InputSimple(InputSimple::Rename) => EventAction::rename(status),
454 Self::InputSimple(InputSimple::Chmod) => EventAction::chmod(status),
455 Self::InputSimple(InputSimple::Newfile) => EventAction::new_file(status),
456 Self::InputSimple(InputSimple::Newdir) => EventAction::new_dir(status),
457 Self::InputSimple(InputSimple::RegexMatch) => EventAction::regex_match(status),
458 Self::InputSimple(InputSimple::Sort) => EventAction::sort(status),
459 Self::InputSimple(InputSimple::Filter) => EventAction::filter(status),
460 Self::InputSimple(InputSimple::SetNvimAddr) => EventAction::set_nvim_server(status),
461 Self::InputSimple(InputSimple::Password(_mount_action, _usage)) => unreachable!("Can't pick a password, those aren't saved."),
462 Self::InputSimple(InputSimple::ShellCommand) => EventAction::shell_command(status),
463 Self::InputSimple(InputSimple::Remote) => EventAction::remote_mount(status),
464 Self::InputSimple(InputSimple::CloudNewdir) => EventAction::cloud_enter_newdir_mode(status),
465 Self::Navigate(Navigate::History) => EventAction::history(status),
466 Self::Navigate(Navigate::Shortcut) => EventAction::shortcut(status),
467 Self::Navigate(Navigate::Trash) => EventAction::trash_open(status),
468 Self::Navigate(Navigate::Marks(markaction)) => match markaction {
469 MarkAction::Jump => EventAction::marks_jump(status),
470 MarkAction::New => EventAction::marks_new(status),
471 },
472 Self::Navigate(Navigate::TempMarks(markaction)) => match markaction {
473 MarkAction::Jump => EventAction::temp_marks_jump(status),
474 MarkAction::New => EventAction::temp_marks_new(status),
475 },
476 Self::Navigate(Navigate::Mount) => EventAction::mount(status),
477 Self::Navigate(Navigate::Picker) => unreachable!("Can't reenter picker from itself"),
478 Self::Navigate(Navigate::Compress) => EventAction::compress(status),
479 Self::Navigate(Navigate::TuiApplication) => EventAction::tui_menu(status),
480 Self::Navigate(Navigate::CliApplication) => EventAction::cli_menu(status),
481 Self::Navigate(Navigate::Context) => EventAction::context(status),
482 Self::Navigate(Navigate::Cloud) => EventAction::cloud_drive(status),
483 Self::Navigate(Navigate::Flagged) => EventAction::display_flagged(status),
484 Self::NeedConfirmation(NeedConfirmation::Copy) => EventAction::copy_paste(status),
485 Self::NeedConfirmation(NeedConfirmation::Delete) => EventAction::delete_file(status),
486 Self::NeedConfirmation(NeedConfirmation::DeleteCloud) => EventAction::cloud_enter_delete_mode(status),
487 Self::NeedConfirmation(NeedConfirmation::Move) => EventAction::cut_paste(status),
488 Self::NeedConfirmation(NeedConfirmation::BulkAction) => EventAction::bulk(status),
489 Self::NeedConfirmation(NeedConfirmation::EmptyTrash) => EventAction::trash_empty(status),
490 Self::Nothing => Ok(()),
491 }
492 }
493}
494
495#[derive(Default, PartialEq, Clone, Copy)]
499pub enum Display {
500 #[default]
501 Directory,
503 Tree,
505 Preview,
507 Fuzzy,
509}
510
511impl Display {
512 fn is(&self, other: Self) -> bool {
513 self == &other
514 }
515
516 pub fn is_tree(&self) -> bool {
517 self.is(Self::Tree)
518 }
519
520 pub fn is_preview(&self) -> bool {
521 self.is(Self::Preview)
522 }
523
524 pub fn is_fuzzy(&self) -> bool {
525 self.is(Self::Fuzzy)
526 }
527
528 pub fn is_directory(&self) -> bool {
529 self.is(Self::Directory)
530 }
531}