Repository: Canop/broot (language: rust)

Issue:
Something like :panel_right_no_open for staging area -- :focus_staging_no_open ?
Maybe I'm thinking about this wrong, but basically, sometimes I add a bunch of files to the staging area, then want to `rm` them. But the way it works makes it easy to accidentally `rm` some selected file in the left pane, whereas I want `rm` to apply to the staging area if and when there *is* one.

So I would want to define my own `rm` verb that does something like `:focus_staging_no_open` (has no effect if there is no staging area, even if there is a right-hand pane), then `rm`. 


Discussion / hints:
Filtering verbs by the kind of panel might find more use.

Something like

```
    {
        invocation: my_rm
        internal: trash
        panels: ["stage"]
    }

```
?

(edit: fixed the example)
Oh, thanks! I'm looking at that filter description (docs say it should be `stage`) but am unclear on the behavior.

If I assign that to the invocation `rm`, and invoke it while the non-staging panel has focus, will that run and apply to the staging panel? And if no staging panel is open, it will continue to do the default `rm` thing?
That's the idea. 
But it looks like this filter is broken (implementation is missing for some verb triggering types). I'll have a look.
With https://github.com/Canop/broot/pull/934, if you define a `rm` with a `panels: ["stage"]` filter, it will be applied when called in the staging area, while the other one will be applied in other panel types.
I think there may have been a misreading of the behavior I'm after, especially as written in my last comment. I just tested with 1.43.0 and the `my_rm` verb from here, with the following setup (Zsh):

```console
$ mkdir rmtest
$ touch rmtest/do_{,not_}trash_{1,2,3}
$ br rmtest
```

I then mark/select the three `do_trash_*` files, and move focus to one of the `do_not_trash_*` files.

If I try `my_rm` here it says "verb not found" (because of the filter). I was looking for something that would act on the non-focused staging panel. 

But also, when I change focus to the staging panel and try the `my_rm` verb, nothing happens when I hit enter (it still says "Hit enter to my_rm: :trash").
> I think there may have been a misreading of the behavior I'm after

This is exact. I reopened.

Fix patch:
diff --git a/src/app/app.rs b/src/app/app.rs
index a40f3869..276070e7 100644
--- a/src/app/app.rs
+++ b/src/app/app.rs
@@ -307,6 +307,7 @@ impl App {
     ) -> Result<(), ProgramError> {
         use CmdResult::*;
         let mut error: Option<String> = None;
+        let mut new_active_panel_idx = None;
         let is_input_invocation = cmd.is_verb_invocated_from_input();
         let app_cmd_context = AppCmdContext {
             panel_skin,
@@ -406,41 +407,30 @@ impl App {
                         debug!("cmd on escape: {cmd:?}");
                         self.apply_command(w, cmd, panel_skin, app_state, con)?;
                     }
-                    Internal::panel_left_no_open | Internal::panel_right_no_open => {
-                        let new_active_panel_idx = if internal == Internal::panel_left_no_open {
-                            // we're here because the state wants us to either move to the panel
-                            // to the left, or close the rightest one
-                            if self.active_panel_idx == 0 {
-                                self.close_panel(self.panels.len().get() - 1, con);
-                                None
-                            } else {
-                                Some(self.active_panel_idx - 1)
-                            }
+                    Internal::focus_staging_area_no_open => {
+                        new_active_panel_idx = self
+                            .panels
+                            .iter()
+                            .position(|p| p.state().get_type() == PanelStateType::Stage);
+                    }
+                    Internal::panel_left_no_open => {
+                        // we're here because the state wants us to either move to the panel
+                        // to the left, or close the rightest one
+                        new_active_panel_idx = if self.active_panel_idx == 0 {
+                            self.close_panel(self.panels.len().get() - 1, con);
+                            None
                         } else {
-                            // panel_right
-                            // we either move to the right or close the leftest panel
-                            if self.active_panel_idx + 1 == self.panels.len().get() {
-                                self.close_panel(0, con);
-                                None
-                            } else {
-                                Some(self.active_panel_idx + 1)
-                            }
+                            Some(self.active_panel_idx - 1)
+                        };
+                    }
+                    Internal::panel_right_no_open => {
+                        // we either move to the right or close the leftest panel
+                        new_active_panel_idx = if self.active_panel_idx + 1 == self.panels.len().get() {
+                            self.close_panel(0, con);
+                            None
+                        } else {
+                            Some(self.active_panel_idx + 1)
                         };
-                        if let Some(idx) = new_active_panel_idx {
-                            if is_input_invocation {
-                                self.mut_panel().clear_input();
-                            }
-                            self.active_panel_idx = idx;
-                            let app_cmd_context = AppCmdContext {
-                                panel_skin,
-                                preview_panel: self.preview_panel,
-                                stage_panel: self.stage_panel,
-                                screen: self.screen,
-                                con,
-                            };
-                            self.mut_panel()
-                                .refresh_input_status(app_state, &app_cmd_context);
-                        }
                     }
                     Internal::search_again => {
                         if let Some(raw_pattern) = &self.panel().last_raw_pattern {
@@ -604,6 +594,22 @@ impl App {
             self.mut_panel().set_error(text);
         }
 
+        if let Some(idx) = new_active_panel_idx {
+            if is_input_invocation {
+                self.mut_panel().clear_input();
+            }
+            self.active_panel_idx = idx;
+            let app_cmd_context = AppCmdContext {
+                panel_skin,
+                preview_panel: self.preview_panel,
+                stage_panel: self.stage_panel,
+                screen: self.screen,
+                con,
+            };
+            self.mut_panel()
+                .refresh_input_status(app_state, &app_cmd_context);
+        }
+
         app_state.other_panel_path = self.get_other_panel_path();
         if let Some(path) = self.state().tree_root() {
             app_state.root = path.to_path_buf();
diff --git a/src/app/panel_state.rs b/src/app/panel_state.rs
index 31b4cc24..7d11804d 100644
--- a/src/app/panel_state.rs
+++ b/src/app/panel_state.rs
@@ -585,9 +585,16 @@ pub trait PanelState {
             Internal::escape => {
                 CmdResult::HandleInApp(Internal::escape)
             }
+            Internal::focus_staging_area_no_open => {
+                CmdResult::HandleInApp(Internal::focus_staging_area_no_open)
+            }
+            // panel_left depends on the kind of panel and is usually handled
+            // in a specific state, contrary to panel_left_no_open
             Internal::panel_left | Internal::panel_left_no_open => {
                 CmdResult::HandleInApp(Internal::panel_left_no_open)
             }
+            // panel_right depends on the kind of panel and is usually handled
+            // in a specific state, contrary to panel_right_no_open
             Internal::panel_right | Internal::panel_right_no_open => {
                 CmdResult::HandleInApp(Internal::panel_right_no_open)
             }
diff --git a/src/verb/internal.rs b/src/verb/internal.rs
index a0f5a7e3..c2d59bdb 100644
--- a/src/verb/internal.rs
+++ b/src/verb/internal.rs
@@ -67,6 +67,7 @@ Internals! {
     escape: "escape from edition, completion, page, etc." false,
     filesystems: "list mounted filesystems" false,
     focus: "display the directory (mapped to *enter*)" true,
+    focus_staging_area_no_open: "focus the staging area if already open" false,
     help: "display broot's help" false,
     input_clear: "empty the input" false,
     input_del_char_below: "delete the char left at the cursor's position" false,
diff --git a/src/verb/verb.rs b/src/verb/verb.rs
index 10517061..b8d25067 100644
--- a/src/verb/verb.rs
+++ b/src/verb/verb.rs
@@ -261,11 +261,9 @@ impl Verb {
             )
         };
         if let VerbExecution::Sequence(seq_ex) = &self.execution {
-            let exec_desc = builder().shell_exec_string(
-                &ExecPattern::from_string(&seq_ex.sequence.raw),
-                con,
-            );
-            format!("Hit *enter* to **{}**: `{}`", name, &exec_desc)
+            // We can't determine before execution what will be the arguments, except
+            // for the first item of the sequence. It's cleaner to just not try expand it
+            format!("Hit *enter* to **{}**: `{}`", name, seq_ex.sequence.raw)
         } else if let VerbExecution::External(external_exec) = &self.execution {
             let exec_desc = builder().shell_exec_string(&external_exec.exec_pattern, con);
             format!("Hit *enter* to **{}**: `{}`", name, &exec_desc)
diff --git a/src/verb/verb_store.rs b/src/verb/verb_store.rs
index 07b2aa91..af1b8442 100644
--- a/src/verb/verb_store.rs
+++ b/src/verb/verb_store.rs
@@ -172,6 +172,7 @@ impl VerbStore {
         #[cfg(unix)]
         self.add_internal(filesystems)
             .with_shortcut("fs");
+        self.add_internal(focus_staging_area_no_open);
         // :focus is also hardcoded on Enter on directories
         // but ctrl-f is useful for focusing on a file's parent
         // (and keep the filter)


Test patch: