Canop / broot

A new way to see and navigate directory trees : https://dystroy.org/broot
MIT License
10.49k stars 228 forks source link

feature request: Fuzzy verb matching #642

Open stinos opened 1 year ago

stinos commented 1 year ago

Short example: unless I'm missing something the only way to get :copy_path executed is starting typing it in full until you're at :copy_p because that is the only match. That's a lot of typing when being used to fuzzy matching :) My muscle memory just wants to do something like :copa which would fuzzy match copy_path first. Shortcuts could solve this partly but configuring and learning them by heart isn't really for me me.

I didn't look in the code but I'd assume it's a matter of replacing 'starts with query or equals some shortcut' with 'fuzzy matches query': note I left out shortcuts because I guess if this were to be implemented it should be a configurable mode since shortcuts and fuzzy matching together might clash too much.

Canop commented 1 year ago

Yes, that's something I'd like to do. Putting it higher in the TODO list.

Side note: you're supposed to use a key combination for :copy_path, not to type it yourself

stinos commented 1 year ago

you're supposed to use a key combination

I'm not completely following, can you explain in detail (sorry I don't use broot enough but am getting into it again).

Canop commented 1 year ago

I mean that most internals aren't really designed to be typed but to be either used in "scripts" (calling broot with --cmd), used in more complex verbs defining sequences, or just bound to key combinations (i.e. like ctrl-c).

This being said, you can call most of them directly by typing them. And I agree with you that fuzzy search would probably make that easier.

stinos commented 1 year ago

Ok, clear. So just to clarify why I'm after it: the way to use a tool shifts with how how much experience one has with it before I've figured out which functionality I use most and then start putting mental effort into creating/remembering shortcut keys or sequences etc, I first have to discover what is even possible. All of which I can read in the help and the manual, but constantly going back and forth that and broot is not much fun nor fast. But that's where fuzzy search really shines: one can just browse files and then go 'hmm ok would be great if I can now copy the path (without having to decide on beforehand whether or not I want to quit broot after that), so just enter 'coppa' and see if it sticks'.

stinos commented 1 month ago

So, even after using broot for a while it still feels awkward without fuzzy matching because basically every other tool I use has it. I had a go at implementing a POC and this works great plus usually still works with shortcuts because they're the only match. However in case of multiple matches it's possible one has to type more; e.g. print_t would originally only match print_tree but now matches print_relative_path as well so print_tr is needed now. That can be fixed though, but I'm first just checking if this would be ok for a PR. Also wondering if this should be made into a mode so one can select to:

Because this fuzzy matching has the best match first, I was also thinking in general (i.e. also without fuzzy matching) that it might be a good addition to, in case of multiple matches, make Enter use the first verb displayed instead of requiring to enter more text to shrink the result until there's only one.

Note because broot displays the text typed so far in the status bar when there's only one match, instead of the full verb name which would imo be better in any case, this might be extra confusing so I think that should be fixed first?

diff --git a/Cargo.toml b/Cargo.toml
index 8deaaa4..1ca5c00 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -44,6 +44,7 @@ include_dir = "0.7"
 lazy-regex = "3.1.0"
 libc = "0.2"
 memmap2 = "0.9"
+nucleo-matcher = "0.3.1"
 once_cell = "1.18" # waiting for https://github.com/rust-lang/rust/issues/109736
 opener = "0.6"
 pathdiff = "0.2"

diff --git a/src/verb/verb_store.rs b/src/verb/verb_store.rs
index cd20217..aec214d 100644
--- a/src/verb/verb_store.rs
+++ b/src/verb/verb_store.rs
@@ -14,6 +14,10 @@ use {
         verb::*,
     },
     crokey::*,
+    nucleo_matcher::{
+        pattern::{Atom, AtomKind, CaseMatching, Normalization},
+        Matcher,
+    }
 };

 /// Provide access to the verbs:
@@ -591,7 +595,7 @@ impl VerbStore {
                 continue;
             }
             for name in &verb.names {
-                if name.starts_with(prefix) {
+                if name.starts_with(prefix.chars().next().unwrap()) {
                     if short_circuit && name == prefix {
                         return PrefixSearchResult::Match(name, verb);
                     }
@@ -605,7 +609,16 @@ impl VerbStore {
         match nb_found {
             0 => PrefixSearchResult::NoMatch,
             1 => PrefixSearchResult::Match(completions[0], &self.verbs[found_index]),
-            _ => PrefixSearchResult::Matches(completions),
+            _ => {
+                let mut matcher = Matcher::default();
+                completions = Atom::new(prefix, CaseMatching::Ignore, Normalization::Never, AtomKind::Fuzzy, false)
+                     .match_list(&completions, &mut matcher).into_iter().map(|v| *v.0).collect::<Vec<&str>>();
+                match completions.len() {
+                    1 => PrefixSearchResult::Match(completions[0], self.verbs.iter().find(|v| v.has_name(completions[0])).unwrap()),
+                    _ => PrefixSearchResult::Matches(completions),
+                }
+            },
         }
     }

What do you think?