1 use {
2     super::*,
3     crate::{
4         command::*,
5         display::{Screen, W},
6         errors::ProgramError,
7         flag::Flag,
8         help::HelpState,
9         pattern::*,
10         preview::{PreviewMode, PreviewState},
11         print,
12         stage::*,
13         task_sync::Dam,
14         tree::*,
15         verb::*,
16     },
17     std::{
18         path::{Path, PathBuf},
19         str::FromStr,
20     },
21 };
22 
23 /// a panel state, stackable to allow reverting
24 ///  to a previous one
25 pub trait PanelState {
26 
get_type(&self) -> PanelStateType27     fn get_type(&self) -> PanelStateType;
28 
set_mode(&mut self, mode: Mode)29     fn set_mode(&mut self, mode: Mode);
get_mode(&self) -> Mode30     fn get_mode(&self) -> Mode;
31 
32     /// called on start of on_command
clear_pending(&mut self)33     fn clear_pending(&mut self) {}
34 
on_click( &mut self, _x: u16, _y: u16, _screen: Screen, _con: &AppContext, ) -> Result<CmdResult, ProgramError>35     fn on_click(
36         &mut self,
37         _x: u16,
38         _y: u16,
39         _screen: Screen,
40         _con: &AppContext,
41     ) -> Result<CmdResult, ProgramError> {
42         Ok(CmdResult::Keep)
43     }
44 
on_double_click( &mut self, _x: u16, _y: u16, _screen: Screen, _con: &AppContext, ) -> Result<CmdResult, ProgramError>45     fn on_double_click(
46         &mut self,
47         _x: u16,
48         _y: u16,
49         _screen: Screen,
50         _con: &AppContext,
51     ) -> Result<CmdResult, ProgramError> {
52         Ok(CmdResult::Keep)
53     }
54 
on_pattern( &mut self, _pat: InputPattern, _app_state: &AppState, _con: &AppContext, ) -> Result<CmdResult, ProgramError>55     fn on_pattern(
56         &mut self,
57         _pat: InputPattern,
58         _app_state: &AppState,
59         _con: &AppContext,
60     ) -> Result<CmdResult, ProgramError> {
61         Ok(CmdResult::Keep)
62     }
63 
on_mode_verb( &mut self, mode: Mode, con: &AppContext, ) -> CmdResult64     fn on_mode_verb(
65         &mut self,
66         mode: Mode,
67         con: &AppContext,
68     ) -> CmdResult {
69         if con.modal {
70             self.set_mode(mode);
71             CmdResult::Keep
72         } else {
73             CmdResult::error("modal mode not enabled in configuration")
74         }
75     }
76 
77     /// execute the internal with the optional given invocation.
78     ///
79     /// The invocation comes from the input and may be related
80     /// to a different verb (the verb may have been triggered
81     /// by a key shortcut)
on_internal( &mut self, w: &mut W, internal_exec: &InternalExecution, input_invocation: Option<&VerbInvocation>, trigger_type: TriggerType, app_state: &mut AppState, cc: &CmdContext, ) -> Result<CmdResult, ProgramError>82     fn on_internal(
83         &mut self,
84         w: &mut W,
85         internal_exec: &InternalExecution,
86         input_invocation: Option<&VerbInvocation>,
87         trigger_type: TriggerType,
88         app_state: &mut AppState,
89         cc: &CmdContext,
90     ) -> Result<CmdResult, ProgramError>;
91 
92     /// a generic implementation of on_internal which may be
93     /// called by states when they don't have a specific
94     /// behavior to execute
on_internal_generic( &mut self, _w: &mut W, internal_exec: &InternalExecution, input_invocation: Option<&VerbInvocation>, _trigger_type: TriggerType, app_state: &mut AppState, cc: &CmdContext, ) -> Result<CmdResult, ProgramError>95     fn on_internal_generic(
96         &mut self,
97         _w: &mut W,
98         internal_exec: &InternalExecution,
99         input_invocation: Option<&VerbInvocation>,
100         _trigger_type: TriggerType,
101         app_state: &mut AppState,
102         cc: &CmdContext,
103     ) -> Result<CmdResult, ProgramError> {
104         let con = &cc.app.con;
105         let screen = cc.app.screen;
106         let bang = input_invocation
107             .map(|inv| inv.bang)
108             .unwrap_or(internal_exec.bang);
109         Ok(match internal_exec.internal {
110             Internal::back => CmdResult::PopState,
111             Internal::copy_line | Internal::copy_path => {
112                 #[cfg(not(feature = "clipboard"))]
113                 {
114                     CmdResult::error("Clipboard feature not enabled at compilation")
115                 }
116                 #[cfg(feature = "clipboard")]
117                 {
118                     if let Some(path) = self.selected_path() {
119                         let path = path.to_string_lossy().to_string();
120                         match terminal_clipboard::set_string(path) {
121                             Ok(()) => CmdResult::Keep,
122                             Err(_) => CmdResult::error("Clipboard error while copying path"),
123                         }
124                     } else {
125                         CmdResult::error("Nothing to copy")
126                     }
127                 }
128             }
129             Internal::close_panel_ok => CmdResult::ClosePanel {
130                 validate_purpose: true,
131                 panel_ref: PanelReference::Active,
132             },
133             Internal::close_panel_cancel => CmdResult::ClosePanel {
134                 validate_purpose: false,
135                 panel_ref: PanelReference::Active,
136             },
137             #[cfg(unix)]
138             Internal::filesystems => {
139                 let fs_state = crate::filesystems::FilesystemState::new(
140                     self.selected_path(),
141                     self.tree_options(),
142                     con,
143                 );
144                 match fs_state {
145                     Ok(state) => {
146                         let bang = input_invocation
147                             .map(|inv| inv.bang)
148                             .unwrap_or(internal_exec.bang);
149                         if bang && cc.app.preview_panel.is_none() {
150                             CmdResult::NewPanel {
151                                 state: Box::new(state),
152                                 purpose: PanelPurpose::None,
153                                 direction: HDir::Right,
154                             }
155                         } else {
156                             CmdResult::NewState(Box::new(state))
157                         }
158                     }
159                     Err(e) => CmdResult::DisplayError(format!("{}", e)),
160                 }
161             }
162             Internal::help => {
163                 let bang = input_invocation
164                     .map(|inv| inv.bang)
165                     .unwrap_or(internal_exec.bang);
166                 if bang && cc.app.preview_panel.is_none() {
167                     CmdResult::NewPanel {
168                         state: Box::new(HelpState::new(self.tree_options(), screen, con)),
169                         purpose: PanelPurpose::None,
170                         direction: HDir::Right,
171                     }
172                 } else {
173                     CmdResult::NewState(Box::new(
174                             HelpState::new(self.tree_options(), screen, con)
175                     ))
176                 }
177             }
178             Internal::mode_input => self.on_mode_verb(Mode::Input, con),
179             Internal::mode_command => self.on_mode_verb(Mode::Command, con),
180             Internal::open_leave => {
181                 if let Some(selection) = self.selection() {
182                     selection.to_opener(con)?
183                 } else {
184                     CmdResult::error("no selection to open")
185                 }
186             }
187             Internal::open_preview => self.open_preview(None, false, cc),
188             Internal::preview_image => self.open_preview(Some(PreviewMode::Image), false, cc),
189             Internal::preview_text => self.open_preview(Some(PreviewMode::Text), false, cc),
190             Internal::preview_binary => self.open_preview(Some(PreviewMode::Hex), false, cc),
191             Internal::toggle_preview => self.open_preview(None, true, cc),
192             Internal::sort_by_count => self.with_new_options(
193                 screen,
194                 &|o| {
195                     if o.sort == Sort::Count {
196                         o.sort = Sort::None;
197                         o.show_counts = false;
198                     } else {
199                         o.sort = Sort::Count;
200                         o.show_counts = true;
201                     }
202                 },
203                 bang,
204                 con,
205             ),
206             Internal::sort_by_date => self.with_new_options(
207                 screen,
208                 &|o| {
209                     if o.sort == Sort::Date {
210                         o.sort = Sort::None;
211                         o.show_dates = false;
212                     } else {
213                         o.sort = Sort::Date;
214                         o.show_dates = true;
215                     }
216                 },
217                 bang,
218                 con,
219             ),
220             Internal::sort_by_size => self.with_new_options(
221                 screen,
222                 &|o| {
223                     if o.sort == Sort::Size {
224                         o.sort = Sort::None;
225                         o.show_sizes = false;
226                     } else {
227                         o.sort = Sort::Size;
228                         o.show_sizes = true;
229                         o.show_root_fs = true;
230                     }
231                 },
232                 bang,
233                 con,
234             ),
235             Internal::no_sort => self.with_new_options(screen, &|o| o.sort = Sort::None, bang, con),
236             Internal::toggle_counts => {
237                 self.with_new_options(screen, &|o| o.show_counts ^= true, bang, con)
238             }
239             Internal::toggle_dates => {
240                 self.with_new_options(screen, &|o| o.show_dates ^= true, bang, con)
241             }
242             Internal::toggle_device_id => {
243                 self.with_new_options(screen, &|o| o.show_device_id ^= true, bang, con)
244             }
245             Internal::toggle_files => {
246                 self.with_new_options(screen, &|o: &mut TreeOptions| o.only_folders ^= true, bang, con)
247             }
248             Internal::toggle_hidden => {
249                 self.with_new_options(screen, &|o| o.show_hidden ^= true, bang, con)
250             }
251             Internal::toggle_root_fs => {
252                 self.with_new_options(screen, &|o| o.show_root_fs ^= true, bang, con)
253             }
254             Internal::toggle_git_ignore => {
255                 self.with_new_options(screen, &|o| o.respect_git_ignore ^= true, bang, con)
256             }
257             Internal::toggle_git_file_info => {
258                 self.with_new_options(screen, &|o| o.show_git_file_info ^= true, bang, con)
259             }
260             Internal::toggle_git_status => {
261                 self.with_new_options(
262                     screen, &|o| {
263                         if o.filter_by_git_status {
264                             o.filter_by_git_status = false;
265                         } else {
266                             o.filter_by_git_status = true;
267                             o.show_hidden = true;
268                         }
269                     }, bang, con
270                 )
271             }
272             Internal::toggle_perm => {
273                 self.with_new_options(screen, &|o| o.show_permissions ^= true, bang, con)
274             }
275             Internal::toggle_sizes => self.with_new_options(
276                 screen,
277                 &|o| {
278                     if o.show_sizes {
279                         o.show_sizes = false;
280                         o.show_root_fs = false;
281                     } else {
282                         o.show_sizes = true;
283                         o.show_root_fs = true;
284                     }
285                 },
286                 bang,
287                 con,
288             ),
289             Internal::toggle_trim_root => {
290                 self.with_new_options(screen, &|o| o.trim_root ^= true, bang, con)
291             }
292             Internal::close_preview => {
293                 if let Some(id) = cc.app.preview_panel {
294                     CmdResult::ClosePanel {
295                         validate_purpose: false,
296                         panel_ref: PanelReference::Id(id),
297                     }
298                 } else {
299                     CmdResult::Keep
300                 }
301             }
302             Internal::panel_left => {
303                 CmdResult::HandleInApp(Internal::panel_left)
304             }
305             Internal::panel_right => {
306                 CmdResult::HandleInApp(Internal::panel_right)
307             }
308             Internal::toggle_second_tree => {
309                 CmdResult::HandleInApp(Internal::toggle_second_tree)
310             }
311             Internal::clear_stage => {
312                 app_state.stage.clear();
313                 if let Some(panel_id) = cc.app.stage_panel {
314                     CmdResult::ClosePanel {
315                         validate_purpose: false,
316                         panel_ref: PanelReference::Id(panel_id),
317                     }
318                 } else {
319                     CmdResult::Keep
320                 }
321             }
322             Internal::stage => self.stage(app_state, cc, con),
323             Internal::unstage => self.unstage(app_state, cc, con),
324             Internal::toggle_stage => self.toggle_stage(app_state, cc, con),
325             Internal::close_staging_area => {
326                 if let Some(id) = cc.app.stage_panel {
327                     CmdResult::ClosePanel {
328                         validate_purpose: false,
329                         panel_ref: PanelReference::Id(id),
330                     }
331                 } else {
332                     CmdResult::Keep
333                 }
334             }
335             Internal::open_staging_area => {
336                 if cc.app.stage_panel.is_none() {
337                     CmdResult::NewPanel {
338                         state: Box::new(StageState::new(app_state, self.tree_options(), con)),
339                         purpose: PanelPurpose::None,
340                         direction: HDir::Right,
341                     }
342                 } else {
343                     CmdResult::Keep
344                 }
345             }
346             Internal::toggle_staging_area => {
347                 if let Some(id) = cc.app.stage_panel {
348                     CmdResult::ClosePanel {
349                         validate_purpose: false,
350                         panel_ref: PanelReference::Id(id),
351                     }
352                 } else {
353                     CmdResult::NewPanel {
354                         state: Box::new(StageState::new(app_state, self.tree_options(), con)),
355                         purpose: PanelPurpose::None,
356                         direction: HDir::Right,
357                     }
358                 }
359             }
360             Internal::print_path => print::print_paths(&self.sel_info(app_state), con)?,
361             Internal::print_relative_path => print::print_relative_paths(&self.sel_info(app_state), con)?,
362             Internal::refresh => CmdResult::RefreshState { clear_cache: true },
363             Internal::quit => CmdResult::Quit,
364             _ => CmdResult::Keep,
365         })
366     }
367 
stage( &self, app_state: &mut AppState, cc: &CmdContext, con: &AppContext, ) -> CmdResult368     fn stage(
369         &self,
370         app_state: &mut AppState,
371         cc: &CmdContext,
372         con: &AppContext,
373     ) -> CmdResult {
374         if let Some(path) = self.selected_path() {
375             let path = path.to_path_buf();
376             app_state.stage.add(path);
377             if cc.app.stage_panel.is_none() {
378                 return CmdResult::NewPanel {
379                     state: Box::new(StageState::new(app_state, self.tree_options(), con)),
380                     purpose: PanelPurpose::None,
381                     direction: HDir::Right,
382                 };
383             }
384         } else {
385             // TODO display error ?
386             warn!("no path in state");
387         }
388         CmdResult::Keep
389     }
390 
unstage( &self, app_state: &mut AppState, cc: &CmdContext, _con: &AppContext, ) -> CmdResult391     fn unstage(
392         &self,
393         app_state: &mut AppState,
394         cc: &CmdContext,
395         _con: &AppContext,
396     ) -> CmdResult {
397         if let Some(path) = self.selected_path() {
398             if app_state.stage.remove(path) && app_state.stage.is_empty() {
399                 if let Some(panel_id) = cc.app.stage_panel {
400                     return CmdResult::ClosePanel {
401                         validate_purpose: false,
402                         panel_ref: PanelReference::Id(panel_id),
403                     };
404                 }
405             }
406         }
407         CmdResult::Keep
408     }
409 
toggle_stage( &self, app_state: &mut AppState, cc: &CmdContext, con: &AppContext, ) -> CmdResult410     fn toggle_stage(
411         &self,
412         app_state: &mut AppState,
413         cc: &CmdContext,
414         con: &AppContext,
415     ) -> CmdResult {
416         if let Some(path) = self.selected_path() {
417             if app_state.stage.contains(path) {
418                 self.unstage(app_state, cc, con)
419             } else {
420                 self.stage(app_state, cc, con)
421             }
422         } else {
423             CmdResult::error("no selection")
424         }
425     }
426 
execute_verb( &mut self, w: &mut W, verb: &Verb, invocation: Option<&VerbInvocation>, trigger_type: TriggerType, app_state: &mut AppState, cc: &CmdContext, ) -> Result<CmdResult, ProgramError>427     fn execute_verb(
428         &mut self,
429         w: &mut W, // needed because we may want to switch from alternate in some externals
430         verb: &Verb,
431         invocation: Option<&VerbInvocation>,
432         trigger_type: TriggerType,
433         app_state: &mut AppState,
434         cc: &CmdContext,
435     ) -> Result<CmdResult, ProgramError> {
436         if verb.needs_selection && !self.has_at_least_one_selection(app_state) {
437             return Ok(CmdResult::error("This verb needs a selection"));
438         }
439         if verb.needs_another_panel && app_state.other_panel_path.is_none() {
440             return Ok(CmdResult::error("This verb needs another panel"));
441         }
442         match &verb.execution {
443             VerbExecution::Internal(internal_exec) => {
444                 self.on_internal(w, internal_exec, invocation, trigger_type, app_state, cc)
445             }
446             VerbExecution::External(external) => {
447                 self.execute_external(w, verb, external, invocation, app_state, cc)
448             }
449             VerbExecution::Sequence(seq_ex) => {
450                 self.execute_sequence(w, verb, seq_ex, invocation, app_state, cc)
451             }
452         }
453     }
454 
execute_external( &mut self, w: &mut W, verb: &Verb, external_execution: &ExternalExecution, invocation: Option<&VerbInvocation>, app_state: &mut AppState, cc: &CmdContext, ) -> Result<CmdResult, ProgramError>455     fn execute_external(
456         &mut self,
457         w: &mut W,
458         verb: &Verb,
459         external_execution: &ExternalExecution,
460         invocation: Option<&VerbInvocation>,
461         app_state: &mut AppState,
462         cc: &CmdContext,
463     ) -> Result<CmdResult, ProgramError> {
464         let sel_info = self.sel_info(app_state);
465         if let Some(invocation) = &invocation {
466             if let Some(error) = verb.check_args(&sel_info, invocation, &app_state.other_panel_path) {
467                 debug!("verb.check_args prevented execution: {:?}", &error);
468                 return Ok(CmdResult::error(error));
469             }
470         }
471         let exec_builder = ExecutionStringBuilder::with_invocation(
472             &verb.invocation_parser,
473             sel_info,
474             app_state,
475             if let Some(inv) = invocation {
476                 &inv.args
477             } else {
478                 &None
479             },
480         );
481         external_execution.to_cmd_result(w, exec_builder, cc.app.con)
482     }
483 
execute_sequence( &mut self, _w: &mut W, verb: &Verb, seq_ex: &SequenceExecution, invocation: Option<&VerbInvocation>, app_state: &mut AppState, _cc: &CmdContext, ) -> Result<CmdResult, ProgramError>484     fn execute_sequence(
485         &mut self,
486         _w: &mut W,
487         verb: &Verb,
488         seq_ex: &SequenceExecution,
489         invocation: Option<&VerbInvocation>,
490         app_state: &mut AppState,
491         _cc: &CmdContext,
492     ) -> Result<CmdResult, ProgramError> {
493         let sel_info = self.sel_info(app_state);
494         if matches!(sel_info, SelInfo::More(_)) {
495             // sequences would be hard to execute as the execution on a file can change the
496             // state in too many ways (changing selection, focused panel, parent, unstage or
497             // stage files, removing the staged paths, etc.)
498             return Ok(CmdResult::error("sequences can't be executed on multiple selections"));
499         }
500         let exec_builder = ExecutionStringBuilder::with_invocation(
501             &verb.invocation_parser,
502             sel_info,
503             app_state,
504             if let Some(inv) = invocation {
505                 &inv.args
506             } else {
507                 &None
508             },
509         );
510         // TODO what follows is dangerous: if an inserted group value contains the separator,
511         // the parsing will cut on this separator
512         let sequence = Sequence {
513             raw: exec_builder.shell_exec_string(&ExecPattern::from_string(&seq_ex.sequence.raw)),
514             separator: seq_ex.sequence.separator.clone(),
515         };
516         Ok(CmdResult::ExecuteSequence { sequence })
517     }
518 
519     /// change the state, does no rendering
on_command( &mut self, w: &mut W, app_state: &mut AppState, cc: &CmdContext, ) -> Result<CmdResult, ProgramError>520     fn on_command(
521         &mut self,
522         w: &mut W,
523         app_state: &mut AppState,
524         cc: &CmdContext,
525     ) -> Result<CmdResult, ProgramError> {
526         self.clear_pending();
527         let con = &cc.app.con;
528         let screen = cc.app.screen;
529         match &cc.cmd {
530             Command::Click(x, y) => self.on_click(*x, *y, screen, con),
531             Command::DoubleClick(x, y) => self.on_double_click(*x, *y, screen, con),
532             Command::PatternEdit { raw, expr } => {
533                 match InputPattern::new(raw.clone(), expr, con) {
534                     Ok(pattern) => self.on_pattern(pattern, app_state, con),
535                     Err(e) => Ok(CmdResult::DisplayError(format!("{}", e))),
536                 }
537             }
538             Command::VerbTrigger {
539                 index,
540                 input_invocation,
541             } => self.execute_verb(
542                 w,
543                 &con.verb_store.verbs[*index],
544                 input_invocation.as_ref(),
545                 TriggerType::Other,
546                 app_state,
547                 cc,
548             ),
549             Command::Internal {
550                 internal,
551                 input_invocation,
552             } => self.on_internal(
553                 w,
554                 &InternalExecution::from_internal(*internal),
555                 input_invocation.as_ref(),
556                 TriggerType::Other,
557                 app_state,
558                 cc,
559             ),
560             Command::VerbInvocate(invocation) => {
561                 let sel_info = self.sel_info(app_state);
562                 match con.verb_store.search_sel_info(
563                     &invocation.name,
564                     &sel_info,
565                 ) {
566                     PrefixSearchResult::Match(_, verb) => {
567                         self.execute_verb(
568                             w,
569                             verb,
570                             Some(invocation),
571                             TriggerType::Input,
572                             app_state,
573                             cc,
574                         )
575                     }
576                     _ => Ok(CmdResult::verb_not_found(&invocation.name)),
577                 }
578             }
579             Command::None | Command::VerbEdit(_) => {
580                 // we do nothing here, the real job is done in get_status
581                 Ok(CmdResult::Keep)
582             }
583         }
584     }
585 
586     /// return a cmdresult asking for the opening of a preview
open_preview( &mut self, prefered_mode: Option<PreviewMode>, close_if_open: bool, cc: &CmdContext, ) -> CmdResult587     fn open_preview(
588         &mut self,
589         prefered_mode: Option<PreviewMode>,
590         close_if_open: bool,
591         cc: &CmdContext,
592     ) -> CmdResult {
593         if let Some(id) = cc.app.preview_panel {
594             if close_if_open {
595                 CmdResult::ClosePanel {
596                     validate_purpose: false,
597                     panel_ref: PanelReference::Id(id),
598                 }
599             } else {
600                 if prefered_mode.is_some() {
601                     // we'll make the preview mode change be
602                     // applied on the preview panel
603                     CmdResult::ApplyOnPanel { id }
604                 } else {
605                     CmdResult::Keep
606                 }
607             }
608         } else {
609             if let Some(path) = self.selected_path() {
610                 if path.is_file() {
611                     CmdResult::NewPanel {
612                         state: Box::new(PreviewState::new(
613                             path.to_path_buf(),
614                             InputPattern::none(),
615                             prefered_mode,
616                             self.tree_options(),
617                             cc.app.con,
618                         )),
619                         purpose: PanelPurpose::Preview,
620                         direction: HDir::Right,
621                     }
622                 } else {
623                     CmdResult::error("only regular files can be previewed")
624                 }
625             } else {
626                 CmdResult::error("no selected file")
627             }
628         }
629     }
630 
631     /// must return None if the state doesn't display a file tree
tree_root(&self) -> Option<&Path>632     fn tree_root(&self) -> Option<&Path> {
633         None
634     }
635 
selected_path(&self) -> Option<&Path>636     fn selected_path(&self) -> Option<&Path>;
637 
selection(&self) -> Option<Selection<'_>>638     fn selection(&self) -> Option<Selection<'_>>;
639 
sel_info<'c>(&'c self, _app_state: &'c AppState) -> SelInfo<'c>640     fn sel_info<'c>(&'c self, _app_state: &'c AppState) -> SelInfo<'c> {
641         // overloaded in stage_state
642         match self.selection() {
643             None => SelInfo::None,
644             Some(selection) => SelInfo::One(selection),
645         }
646     }
647 
has_at_least_one_selection(&self, _app_state: &AppState) -> bool648     fn has_at_least_one_selection(&self, _app_state: &AppState) -> bool {
649         true // overloaded in stage_state
650     }
651 
refresh(&mut self, screen: Screen, con: &AppContext) -> Command652     fn refresh(&mut self, screen: Screen, con: &AppContext) -> Command;
653 
tree_options(&self) -> TreeOptions654     fn tree_options(&self) -> TreeOptions;
655 
656     /// build a cmdResult in response to a command being a change of
657     /// tree options. This may or not be a new state
with_new_options( &mut self, screen: Screen, change_options: &dyn Fn(&mut TreeOptions), in_new_panel: bool, con: &AppContext, ) -> CmdResult658     fn with_new_options(
659         &mut self,
660         screen: Screen,
661         change_options: &dyn Fn(&mut TreeOptions),
662         in_new_panel: bool,
663         con: &AppContext,
664     ) -> CmdResult;
665 
do_pending_task( &mut self, _stage: &Stage, _screen: Screen, _con: &AppContext, _dam: &mut Dam, )666     fn do_pending_task(
667         &mut self,
668         _stage: &Stage,
669         _screen: Screen,
670         _con: &AppContext,
671         _dam: &mut Dam,
672     ) {
673         // no pending task in default impl
674         unreachable!();
675     }
676 
get_pending_task( &self, ) -> Option<&'static str>677     fn get_pending_task(
678         &self,
679     ) -> Option<&'static str> {
680         None
681     }
682 
display( &mut self, w: &mut W, disc: &DisplayContext, ) -> Result<(), ProgramError>683     fn display(
684         &mut self,
685         w: &mut W,
686         disc: &DisplayContext,
687     ) -> Result<(), ProgramError>;
688 
689     /// return the flags to display
get_flags(&self) -> Vec<Flag>690     fn get_flags(&self) -> Vec<Flag> {
691         vec![]
692     }
693 
get_starting_input(&self) -> String694     fn get_starting_input(&self) -> String {
695         String::new()
696     }
697 
set_selected_path(&mut self, _path: PathBuf, _con: &AppContext)698     fn set_selected_path(&mut self, _path: PathBuf, _con: &AppContext) {
699         // this function is useful for preview states
700     }
701 
702     /// return the status which should be used when there's no verb edited
no_verb_status( &self, _has_previous_state: bool, _con: &AppContext, ) -> Status703     fn no_verb_status(
704         &self,
705         _has_previous_state: bool,
706         _con: &AppContext,
707     ) -> Status {
708         Status::from_message(
709             "Hit *esc* to get back, or a space to start a verb"
710         )
711     }
712 
get_status( &self, app_state: &AppState, cc: &CmdContext, has_previous_state: bool, ) -> Status713     fn get_status(
714         &self,
715         app_state: &AppState,
716         cc: &CmdContext,
717         has_previous_state: bool,
718     ) -> Status {
719         match &cc.cmd {
720             Command::PatternEdit { .. } => self.no_verb_status(has_previous_state, cc.app.con),
721             Command::VerbEdit(invocation) => {
722                 if invocation.name.is_empty() {
723                     Status::new(
724                         "Type a verb then *enter* to execute it (*?* for the list of verbs)",
725                         false,
726                     )
727                 } else {
728                     let sel_info = self.sel_info(app_state);
729                     match cc.app.con.verb_store.search_sel_info(
730                         &invocation.name,
731                         &sel_info,
732                     ) {
733                         PrefixSearchResult::NoMatch => {
734                             Status::new("No matching verb (*?* for the list of verbs)", true)
735                         }
736                         PrefixSearchResult::Match(_, verb) => {
737                             self.get_verb_status(verb, invocation, sel_info, cc, app_state)
738                         }
739                         PrefixSearchResult::Matches(completions) => Status::new(
740                             format!(
741                                 "Possible verbs: {}",
742                                 completions
743                                     .iter()
744                                     .map(|c| format!("*{}*", c))
745                                     .collect::<Vec<String>>()
746                                     .join(", "),
747                             ),
748                             false,
749                         ),
750                     }
751                 }
752             }
753             _ => self.no_verb_status(has_previous_state, cc.app.con),
754         }
755     }
756 
get_verb_status( &self, verb: &Verb, invocation: &VerbInvocation, sel_info: SelInfo<'_>, _cc: &CmdContext, app_state: &AppState, ) -> Status757     fn get_verb_status(
758         &self,
759         verb: &Verb,
760         invocation: &VerbInvocation,
761         sel_info: SelInfo<'_>,
762         _cc: &CmdContext,
763         app_state: &AppState,
764     ) -> Status {
765         if sel_info.count_paths() > 1 {
766             if let VerbExecution::External(external) = &verb.execution {
767                 if external.exec_mode != ExternalExecutionMode::StayInBroot {
768                     return Status::new(
769                         "only verbs returning to broot on end can be executed on a multi-selection".to_owned(),
770                         true,
771                     );
772                 }
773             }
774             // right now there's no check for sequences but they're inherently dangereous
775         }
776         if let Some(err) = verb.check_args(&sel_info, invocation, &app_state.other_panel_path) {
777             Status::new(err, true)
778         } else {
779             Status::new(
780                 verb.get_status_markdown(
781                     sel_info,
782                     app_state,
783                     invocation,
784                 ),
785                 false,
786             )
787         }
788     }
789 }
790 
791 
get_arg<T: Copy + FromStr>( verb_invocation: Option<&VerbInvocation>, internal_exec: &InternalExecution, default: T, ) -> T792 pub fn get_arg<T: Copy + FromStr>(
793     verb_invocation: Option<&VerbInvocation>,
794     internal_exec: &InternalExecution,
795     default: T,
796 ) -> T {
797     verb_invocation
798         .and_then(|vi| vi.args.as_ref())
799         .or_else(|| internal_exec.arg.as_ref())
800         .and_then(|s| s.parse::<T>().ok())
801         .unwrap_or(default)
802 }
803 
initial_mode(con: &AppContext) -> Mode804 pub fn initial_mode(con: &AppContext) -> Mode {
805     if con.modal {
806         Mode::Command
807     } else {
808         Mode::Input
809     }
810 }
811