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.

Fix patch:
diff --git a/src/app/panel_state.rs b/src/app/panel_state.rs
index b9d1f1af..31b4cc24 100644
--- a/src/app/panel_state.rs
+++ b/src/app/panel_state.rs
@@ -846,7 +846,12 @@ pub trait PanelState {
                 None
             },
         );
-        let sequence = exec_builder.sequence(&seq_ex.sequence, &cc.app.con.verb_store, cc.app.con);
+        let sequence = exec_builder.sequence(
+            &seq_ex.sequence,
+            &cc.app.con.verb_store,
+            cc.app.con,
+            Some(self.get_type()),
+        );
         Ok(CmdResult::ExecuteSequence { sequence })
     }
 
@@ -897,6 +902,7 @@ pub trait PanelState {
                 match con.verb_store.search_sel_info(
                     &invocation.name,
                     sel_info,
+                    Some(self.get_type()),
                 ) {
                     PrefixSearchResult::Match(_, verb) => {
                         self.execute_verb(
@@ -1061,6 +1067,7 @@ pub trait PanelState {
                     match cc.app.con.verb_store.search_sel_info(
                         &invocation.name,
                         sel_info,
+                        Some(self.get_type()),
                     ) {
                         PrefixSearchResult::NoMatch => {
                             Status::new("No matching verb (*?* for the list of verbs)", true)
diff --git a/src/command/completion.rs b/src/command/completion.rs
index d6493622..8b991a6b 100644
--- a/src/command/completion.rs
+++ b/src/command/completion.rs
@@ -3,6 +3,7 @@ use {
     crate::{
         app::{
             AppContext,
+            PanelStateType,
             SelInfo,
         },
         path::{self, PathAnchor},
@@ -89,8 +90,9 @@ impl Completions {
         start: &str,
         con: &AppContext,
         sel_info: SelInfo<'_>,
+        panel_state_type: Option<PanelStateType>,
     ) -> Self {
-        match con.verb_store.search(start, Some(sel_info), false) {
+        match con.verb_store.search(start, Some(sel_info), false, panel_state_type) {
             PrefixSearchResult::NoMatch => Self::None,
             PrefixSearchResult::Match(name, _) => {
                 if start.len() >= name.len() {
@@ -113,8 +115,9 @@ impl Completions {
         path: &Path,
         sel_info: SelInfo<'_>,
         con: &AppContext,
+        panel_state_type: Option<PanelStateType>,
     ) -> io::Result<Vec<String>> {
-        let anchor = match con.verb_store.search(verb_name, Some(sel_info), false) {
+        let anchor = match con.verb_store.search(verb_name, Some(sel_info), false, panel_state_type) {
             PrefixSearchResult::Match(_, verb) => verb.get_unique_arg_anchor(),
             _ => PathAnchor::Unspecified,
         };
@@ -150,6 +153,7 @@ impl Completions {
         arg: &str,
         con: &AppContext,
         sel_info: SelInfo<'_>,
+        panel_state_type: Option<PanelStateType>,
     ) -> Self {
         if arg.contains(' ') {
             return Self::None;
@@ -157,13 +161,13 @@ impl Completions {
         // we try to get the type of argument
         let arg_def = con
             .verb_store
-            .search_sel_info_unique(verb_name, sel_info)
+            .search_sel_info_unique(verb_name, sel_info, panel_state_type)
             .and_then(|verb| verb.invocation_parser.as_ref())
             .and_then(|invocation_parser| invocation_parser.get_unique_arg_def());
         if matches!(arg_def, Some(ArgDef::Theme)) {
             Self::for_theme_arg(arg)
         } else {
-            Self::for_path_arg(verb_name, arg, con, sel_info)
+            Self::for_path_arg(verb_name, arg, con, sel_info, panel_state_type)
         }
     }
 
@@ -186,6 +190,7 @@ impl Completions {
         arg: &str,
         con: &AppContext,
         sel_info: SelInfo<'_>,
+        panel_state_type: Option<PanelStateType>,
     ) -> Self {
         // in the future we might offer completion of other types
         // of arguments, maybe user supplied, but there's no use case
@@ -196,7 +201,7 @@ impl Completions {
         match &sel_info {
             SelInfo::None => Self::None,
             SelInfo::One(sel) => {
-                match Self::list_for_path(verb_name, arg, sel.path, sel_info, con) {
+                match Self::list_for_path(verb_name, arg, sel.path, sel_info, con, panel_state_type) {
                     Ok(list) => Self::from_list(list),
                     Err(e) => {
                         warn!("Error while trying to complete path: {:?}", e);
@@ -215,7 +220,8 @@ impl Completions {
                                 arg,
                                 path,
                                 sel_info,
-                                con
+                                con,
+                                panel_state_type,
                         ).ok()
                     });
                 let mut list = match lists.next() {
@@ -241,17 +247,18 @@ impl Completions {
         parts: &CommandParts,
         con: &AppContext,
         sel_info: SelInfo<'_>,
+        panel_state_type: Option<PanelStateType>,
     ) -> Self {
         match &parts.verb_invocation {
             Some(invocation) if !invocation.is_empty() => {
                 match &invocation.args {
                     None => {
                         // looking into verb completion
-                        Self::for_verb(&invocation.name, con, sel_info)
+                        Self::for_verb(&invocation.name, con, sel_info, panel_state_type)
                     }
                     Some(args) if !args.is_empty() => {
                         // looking into arg completion
-                        Self::for_arg(&invocation.name, args, con, sel_info)
+                        Self::for_arg(&invocation.name, args, con, sel_info, panel_state_type)
                     }
                     _ => {
                         // nothing possible
diff --git a/src/command/panel_input.rs b/src/command/panel_input.rs
index aae6793a..f8c0934c 100644
--- a/src/command/panel_input.rs
+++ b/src/command/panel_input.rs
@@ -236,6 +236,7 @@ impl PanelInput {
         sel_info: SelInfo<'_>,
         raw: String,
         parts: CommandParts,
+        panel_state_type: Option<PanelStateType>,
     ) -> Command {
         let parts_before_cycle;
         let completable_parts = if let Some(s) = &self.input_before_cycle {
@@ -244,7 +245,7 @@ impl PanelInput {
         } else {
             &parts
         };
-        let completions = Completions::for_input(completable_parts, con, sel_info);
+        let completions = Completions::for_input(completable_parts, con, sel_info, panel_state_type);
         let added = match completions {
             Completions::None => {
                 debug!("nothing to complete!");
@@ -405,7 +406,7 @@ impl PanelInput {
         // 'tab' completion of a verb or one of its arguments
         if Verb::is_some_internal(verb, Internal::next_match) {
             if parts.verb_invocation.is_some() {
-                return self.auto_complete_verb(con, sel_info, raw, parts);
+                return self.auto_complete_verb(con, sel_info, raw, parts, Some(panel_state_type));
             }
             // if no verb is being edited, the state may handle this internal
             // in a specific way
diff --git a/src/command/sequence.rs b/src/command/sequence.rs
index 1a8bb832..77f6e4d1 100644
--- a/src/command/sequence.rs
+++ b/src/command/sequence.rs
@@ -44,6 +44,11 @@ impl Sequence {
             raw,
         }
     }
+    /// Parse the sequence into a vec of commands.
+    ///
+    /// Beware: panel_state_type filtering isn't applied (
+    ///   and would be difficult a priori as we can change panel
+    ///   in the middle of a sequence)
     pub fn parse(
         &self,
         con: &AppContext,
@@ -91,7 +96,7 @@ fn add_commands(
         if let Command::VerbInvocate(invocation) = &command {
             // we check that the verb exists to avoid running a sequence
             // of actions with some missing
-            match con.verb_store.search_prefix(&invocation.name) {
+            match con.verb_store.search_prefix(&invocation.name, None) {
                 PrefixSearchResult::NoMatch => {
                     return Err(ProgramError::UnknownVerb {
                         name: invocation.name.to_string(),
diff --git a/src/verb/execution_builder.rs b/src/verb/execution_builder.rs
index 656267a5..932410df 100644
--- a/src/verb/execution_builder.rs
+++ b/src/verb/execution_builder.rs
@@ -293,6 +293,7 @@ impl<'b> ExecutionStringBuilder<'b> {
         sequence: &Sequence,
         verb_store: &VerbStore,
         con: &AppContext,
+        panel_state_type: Option<PanelStateType>,
     ) -> Sequence {
         let mut inputs = Vec::new();
         for input in sequence.raw.split(&sequence.separator) {
@@ -302,7 +303,7 @@ impl<'b> ExecutionStringBuilder<'b> {
                 .and_then(|vi| {
                     let command = Command::from_parts(vi, true);
                     if let Command::VerbInvocate(invocation) = &command {
-                        let search = verb_store.search_prefix(&invocation.name);
+                        let search = verb_store.search_prefix(&invocation.name, panel_state_type);
                         if let PrefixSearchResult::Match(_, verb) = search {
                             return Some(verb);
                         }
diff --git a/src/verb/verb_store.rs b/src/verb/verb_store.rs
index ea53d063..88c72232 100644
--- a/src/verb/verb_store.rs
+++ b/src/verb/verb_store.rs
@@ -564,15 +564,17 @@ impl VerbStore {
         &'v self,
         prefix: &str,
         sel_info: SelInfo<'_>,
+        panel_state_type: Option<PanelStateType>,
     ) -> PrefixSearchResult<'v, &Verb> {
-        self.search(prefix, Some(sel_info), true)
+        self.search(prefix, Some(sel_info), true, panel_state_type)
     }
 
     pub fn search_prefix<'v>(
         &'v self,
         prefix: &str,
+        panel_state_type: Option<PanelStateType>,
     ) -> PrefixSearchResult<'v, &Verb> {
-        self.search(prefix, None, true)
+        self.search(prefix, None, true, panel_state_type)
     }
 
     /// Return either the only match, or None if there's not
@@ -581,8 +583,9 @@ impl VerbStore {
         &'v self,
         prefix: &str,
         sel_info: SelInfo<'_>,
+        panel_state_type: Option<PanelStateType>,
     ) -> Option<&'v Verb> {
-        match self.search_sel_info(prefix, sel_info) {
+        match self.search_sel_info(prefix, sel_info, panel_state_type) {
             PrefixSearchResult::Match(_, verb) => Some(verb),
             _ => None,
         }
@@ -593,6 +596,7 @@ impl VerbStore {
         prefix: &str,
         sel_info: Option<SelInfo>,
         short_circuit: bool,
+        panel_state_type: Option<PanelStateType>,
     ) -> PrefixSearchResult<'v, &Verb> {
         let mut found_index = 0;
         let mut nb_found = 0;
@@ -605,6 +609,11 @@ impl VerbStore {
                     continue;
                 }
             }
+            if let Some(panel_state_type) = panel_state_type {
+                if !verb.can_be_called_in_panel(panel_state_type) {
+                    continue;
+                }
+            }
             if let Some(count) = sel_count {
                 if count > 1 && verb.is_sequence() {
                     continue;


Test patch: