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