From 09ef4951d58f45521b1cf8fba26f33397197e4ab Mon Sep 17 00:00:00 2001 From: Natsuki Ikeguchi Date: Sat, 29 Jun 2024 14:56:26 +0900 Subject: [PATCH] feat: Introduce search command to perform a fuzzy search --- Cargo.lock | 17 +++++++++++++++++ Cargo.toml | 1 + src/cmd/mod.rs | 18 +++++++++++------- src/cmd/search.rs | 42 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 71 insertions(+), 7 deletions(-) create mode 100644 src/cmd/search.rs diff --git a/Cargo.lock b/Cargo.lock index 37fbdc8..56a3103 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1077,6 +1077,7 @@ dependencies = [ "indicatif", "itertools", "lazy_static", + "nucleo-matcher", "octocrab", "regex", "serde", @@ -1591,6 +1592,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "nucleo-matcher" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf33f538733d1a5a3494b836ba913207f14d9d4a1d3cd67030c5061bdd2cac85" +dependencies = [ + "memchr", + "unicode-segmentation", +] + [[package]] name = "num" version = "0.4.1" @@ -2903,6 +2914,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + [[package]] name = "unicode-width" version = "0.1.11" diff --git a/Cargo.toml b/Cargo.toml index d9be577..fe3ee9b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ git2 = "0.18.3" itertools = "0.13.0" indicatif = "0.17.8" lazy_static = "1.5" +nucleo-matcher = "0.3.1" regex = "1.10" serde = { version = "1.0", features = ["derive"] } serde_regex = "1.1" diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index febb66d..60e3f7d 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -1,3 +1,10 @@ +use std::io::stderr; + +use anyhow::Result; +use clap::{Parser, Subcommand}; +use tracing_subscriber::filter::LevelFilter; +use tracing_subscriber::EnvFilter; + mod add; mod browse; mod cd; @@ -8,17 +15,11 @@ mod list; mod open; mod path; mod profile; +mod search; mod shell; mod sync; mod version; -use std::io::stderr; - -use anyhow::Result; -use clap::{Parser, Subcommand}; -use tracing_subscriber::filter::LevelFilter; -use tracing_subscriber::EnvFilter; - #[derive(Debug, Subcommand)] pub enum Action { /// Add an existing repository into the ghr managed directory. @@ -41,6 +42,8 @@ pub enum Action { Path(path::Cmd), /// Manages profiles to use in repositories. Profile(profile::Cmd), + /// Perform a fuzzy search on the repositories list. + Search(search::Cmd), /// Writes a shell script to extend ghr features. Shell(shell::Cmd), /// Sync repositories between your devices. @@ -93,6 +96,7 @@ impl Cli { Browse(cmd) => cmd.run().await, Path(cmd) => cmd.run(), Profile(cmd) => cmd.run(), + Search(cmd) => cmd.run(), Shell(cmd) => cmd.run(), Sync(cmd) => cmd.run().await, Version(cmd) => cmd.run(), diff --git a/src/cmd/search.rs b/src/cmd/search.rs new file mode 100644 index 0000000..4afda08 --- /dev/null +++ b/src/cmd/search.rs @@ -0,0 +1,42 @@ +use anyhow::Result; +use clap::Parser; +use itertools::Itertools; +use nucleo_matcher::{Config, Matcher, Utf32Str}; + +use crate::repository::Repositories; +use crate::root::Root; + +const MIN_SCORE: u16 = 50; + +#[derive(Debug, Parser)] +pub struct Cmd { + query: String, +} + +impl Cmd { + pub fn run(self) -> Result<()> { + let root = Root::find()?; + + let mut matcher = Matcher::new(Config::DEFAULT); + + Repositories::try_collect(&root)? + .into_iter() + .map(|(path, _)| path.to_string()) + .filter_map(|path| { + let mut haystack_buf = Vec::new(); + let mut needle_buf = Vec::new(); + + matcher + .fuzzy_match( + Utf32Str::new(&path, &mut haystack_buf), + Utf32Str::new(&self.query, &mut needle_buf), + ) + .map(|score| (path, score)) + }) + .filter(|(_, score)| *score > MIN_SCORE) + .sorted_by_key(|(_, score)| -i32::from(*score)) + .for_each(|(path, _)| println!("{}", path)); + + Ok(()) + } +}