Repository: Canop/broot (language: rust)

Issue:
Unbind built-in keybinds
I find some of the built-in keybinds counterintuitive, like `<C-w>`, for example. I expect it to delete the previous word, but by default it closes panels and the program. There are a few other default keybinds that I don't like and there should probably be an option to disable them.


Discussion / hints:
Agreed. I'm quite surprised that there seems to be no way to do this. Since you can define your own key bindings, it seems fairly natural that you might want to unbind the defaults. In my case, I wanted to unbind `input_go_left/right` so I can disambiguate the arrow keys, after mapping those to ctrl-b/-f. As for syntax, perhaps a field in a `verbs` list entry like `force_unique_key` or `unbind_others`? That way there wouldn't be any weirdness about resolution order. Perhaps additionally, maybe one could have a `:unbound` or `:noop` internal for true unbinding.
I forgot this issue, which is why it didn't progress.

I'll do something, probably a kind of no-op verb as you suggest.

The cleanest design would probably be to separate bindings from verb definition but this implies a lot of breaking changes.

No worries! Yeah, the current design of mixing keybindings and verb definitions is a little weird and might get a little weirder if unbinding is added as-is, though on the other hand it wasn't that confusing either, just by reading the docs (while learning broot a few days ago) I kind of groked how to do it without too much difficulty. Either way, it's your call, I'm not going to die if I have to wait a few months for unbinding. I'll be happy about it whenever it arrives.

Cheers, and thanks for your efforts!

Fix patch:
diff --git a/src/app/panel_state.rs b/src/app/panel_state.rs
index a593edf3..705dc02f 100644
--- a/src/app/panel_state.rs
+++ b/src/app/panel_state.rs
@@ -112,7 +112,7 @@ pub trait PanelState {
             .unwrap_or(internal_exec.bang);
         Ok(match internal_exec.internal {
             Internal::apply_flags => {
-                info!("applying flags input_invocation: {:#?}", input_invocation);
+                debug!("applying flags input_invocation: {:#?}", input_invocation);
                 let flags = input_invocation.and_then(|inv| inv.args.as_ref());
                 if let Some(flags) = flags {
                     self.with_new_options(
@@ -1005,7 +1005,6 @@ pub trait PanelState {
                     )
                 } else {
                     let sel_info = self.sel_info(app_state);
-                    info!("invocation: {:#?}", invocation);
                     match cc.app.con.verb_store.search_sel_info(
                         &invocation.name,
                         sel_info,
diff --git a/src/verb/verb_invocation.rs b/src/verb/verb_invocation.rs
index 3831787f..9e2b9bb8 100644
--- a/src/verb/verb_invocation.rs
+++ b/src/verb/verb_invocation.rs
@@ -120,10 +120,8 @@ impl From<&str> for VerbInvocation {
                 continue;
             }
             if name.is_empty() {
-                if c.is_alphabetic() {
-                    name.push(c);
-                } else {
-                    name.push(c);
+                name.push(c);
+                if !c.is_alphabetic() {
                     name_is_special = true;
                 }
                 continue;
diff --git a/src/verb/verb_store.rs b/src/verb/verb_store.rs
index e6019126..68bc516a 100644
--- a/src/verb/verb_store.rs
+++ b/src/verb/verb_store.rs
@@ -25,6 +25,7 @@ use {
 /// - if only one verb name starts with the input
 pub struct VerbStore {
     verbs: Vec<Verb>,
+    unbound_keys: Vec<KeyCombination>,
 }
 
 #[derive(Debug, Clone, PartialEq)]
@@ -36,7 +37,7 @@ pub enum PrefixSearchResult<'v, T> {
 
 impl VerbStore {
     pub fn new(conf: &mut Conf) -> Result<Self, ConfError> {
-        let mut store = Self { verbs: Vec::new() };
+        let mut store = Self { verbs: Vec::new(), unbound_keys: Vec::new() };
         for vc in &conf.verbs {
             if let Err(e) = store.add_from_conf(vc) {
                 eprintln!("Invalid verb configuration: {}", e);
@@ -51,6 +52,9 @@ impl VerbStore {
             }
         }
         store.add_builtin_verbs()?; // at the end so that we can override them
+        for key in store.unbound_keys.clone() {
+            store.unbind_key(key)?;
+        }
         Ok(store)
     }
 
@@ -389,6 +393,23 @@ impl VerbStore {
                 details: "You can't simultaneously have leave_broot=false and from_shell=true".to_string(),
             });
         }
+
+        // we accept both key and keys. We merge both here
+        let mut unchecked_keys = vc.keys.clone();
+        if let Some(key) = &vc.key {
+            unchecked_keys.push(key.clone());
+        }
+        let mut checked_keys = Vec::new();
+        for key in &unchecked_keys {
+            let key = crokey::parse(key)?;
+            if keys::is_reserved(key) {
+                return Err(ConfError::ReservedKey {
+                    key: keys::KEY_FORMAT.to_string(key)
+                });
+            }
+            checked_keys.push(key);
+        }
+
         let invocation = vc.invocation.clone().filter(|i| !i.is_empty());
         let internal = vc.internal.as_ref().filter(|i| !i.is_empty());
         let external = vc.external.as_ref().filter(|i| !i.is_empty());
@@ -443,9 +464,12 @@ impl VerbStore {
                 sequence: Sequence::new(s, cmd_separator),
             }),
             _ => {
-                return Err(ConfError::InvalidVerbConf {
-                    details: "You must define either internal, external or cmd".to_string(),
-                });
+                // there's no execution, this 'verbconf' is supposed to be dedicated to
+                // unbind keys
+                for key in checked_keys {
+                    self.unbound_keys.push(key);
+                }
+                return Ok(());
             }
         };
         let description = vc
@@ -458,21 +482,6 @@ impl VerbStore {
             execution,
             description,
         )?;
-        // we accept both key and keys. We merge both here
-        let mut unchecked_keys = vc.keys.clone();
-        if let Some(key) = &vc.key {
-            unchecked_keys.push(key.clone());
-        }
-        let mut checked_keys = Vec::new();
-        for key in &unchecked_keys {
-            let key = crokey::parse(key)?;
-            if keys::is_reserved(key) {
-                return Err(ConfError::ReservedKey {
-                    key: keys::KEY_FORMAT.to_string(key)
-                });
-            }
-            checked_keys.push(key);
-        }
         for extension in &vc.extensions {
             verb.file_extensions.push(extension.clone());
         }
@@ -492,6 +501,26 @@ impl VerbStore {
         Ok(())
     }
 
+    pub fn unbind_key(
+        &mut self,
+        key: KeyCombination,
+    ) -> Result<(), ConfError> {
+        debug!("unbinding key {:?}", key);
+        for verb in &mut self.verbs {
+            verb.keys.retain(|&k| k != key);
+        }
+        Ok(())
+    }
+    pub fn unbind_name(
+        &mut self,
+        name: &str,
+    ) -> Result<(), ConfError> {
+        for verb in &mut self.verbs {
+            verb.names.retain(|n| n != name);
+        }
+        Ok(())
+    }
+
     pub fn search_sel_info<'v>(
         &'v self,
         prefix: &str,


Test patch: