From 12a9c5c9015c2115874c7ba339b43203f2753e52 Mon Sep 17 00:00:00 2001 From: Alexandre Pasmantier Date: Fri, 27 Sep 2024 00:10:01 +0200 Subject: [PATCH] good progress --- Cargo.lock | 1 + Cargo.toml | 1 + src/components/finders.rs | 3 +- src/components/finders/env.rs | 4 +- src/components/finders/files.rs | 115 ++++++++++++++++------------- src/components/pickers/files.rs | 10 +-- src/components/previewers/files.rs | 1 - src/components/television.rs | 10 ++- 8 files changed, 82 insertions(+), 63 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 48738f7..3bb5a58 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2404,6 +2404,7 @@ dependencies = [ "lazy_static", "libc", "nucleo", + "nucleo-matcher", "pretty_assertions", "ratatui", "rust-devicons", diff --git a/Cargo.toml b/Cargo.toml index 78a01d3..a49c0c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ json5 = "0.4.1" lazy_static = "1.5.0" libc = "0.2.158" nucleo = "0.5.0" +nucleo-matcher = "0.3.1" pretty_assertions = "1.4.0" ratatui = { version = "0.28.1", features = ["serde", "macros"] } rust-devicons = "0.2.2" diff --git a/src/components/finders.rs b/src/components/finders.rs index 297b8ee..0e945ca 100644 --- a/src/components/finders.rs +++ b/src/components/finders.rs @@ -1,4 +1,3 @@ -use futures::Stream; use rust_devicons::FileIcon; mod env; @@ -43,5 +42,5 @@ pub const ENTRY_PLACEHOLDER: Entry = Entry { /// # Methods /// - `find`: Find entries based on a pattern. pub trait Finder { - async fn find(&mut self, pattern: &str) -> impl Stream; + async fn find(&mut self, pattern: &str) -> impl Iterator; } diff --git a/src/components/finders/env.rs b/src/components/finders/env.rs index 3a5b93f..69f947d 100644 --- a/src/components/finders/env.rs +++ b/src/components/finders/env.rs @@ -35,7 +35,7 @@ impl EnvVarFinder { } impl Finder for EnvVarFinder { - async fn find(&mut self, pattern: &str) -> impl Stream { + async fn find(&mut self, pattern: &str) -> impl Iterator { let mut results: Vec = Vec::new(); // try to get from cache if let Some(entries) = self.cache.get(pattern) { @@ -88,6 +88,6 @@ impl Finder for EnvVarFinder { // cache the results self.cache.insert(pattern.to_string(), results.clone()); } - stream::iter(results) + results.into_iter() } } diff --git a/src/components/finders/files.rs b/src/components/finders/files.rs index 204d56b..fd92a04 100644 --- a/src/components/finders/files.rs +++ b/src/components/finders/files.rs @@ -1,13 +1,16 @@ use color_eyre::Result; -use futures::{stream, StreamExt}; -use nucleo::{Config, Item, Nucleo}; +use nucleo::{ + pattern::{CaseMatching, Normalization}, + Config, Injector, Item, Nucleo, Utf32String, +}; +use nucleo_matcher::{Config as LowConfig, Matcher}; use std::{ collections::HashMap, path::{Path, PathBuf}, sync::Arc, }; -use ignore::{types::TypesBuilder, WalkBuilder}; +use ignore::{types::TypesBuilder, DirEntry, WalkBuilder}; use tokio::sync::mpsc::{self, UnboundedSender}; use tracing::info; @@ -20,8 +23,10 @@ use crate::{ pub struct FileFinder { current_directory: PathBuf, files: Vec, - matcher: Option>, + high_matcher: Nucleo, + low_matcher: Matcher, cache: HashMap>, + last_pattern: String, } struct MatchItem { @@ -30,30 +35,53 @@ struct MatchItem { impl FileFinder { pub async fn new() -> Self { - let files = load_files(&std::env::current_dir().unwrap()).await; + let high_matcher = Nucleo::new(Config::DEFAULT.match_paths(), Arc::new(|| {}), None, 1); + let low_matcher = Matcher::new(LowConfig::DEFAULT.match_paths()); + // start loading files in the background + tokio::spawn(load_files( + std::env::current_dir().unwrap(), + high_matcher.injector(), + )); FileFinder { current_directory: std::env::current_dir().unwrap(), - files, - matcher: None, + files: Vec::new(), + high_matcher, + low_matcher, cache: HashMap::new(), + last_pattern: String::new(), } } - - pub fn register_action_handler(&mut self, tx: UnboundedSender) -> Result<()> { - let matcher_config = Config::DEFAULT.match_paths(); - let notify = || { - tx.send(Action::SyncFinderResults).unwrap(); - }; - self.matcher = Some(Nucleo::new(matcher_config, Arc::new(notify), None, 1)); - Ok(()) - } } -const FUZZY_THRESHOLD: i64 = 2; - impl Finder for FileFinder { - async fn find(&mut self, pattern: &str) -> impl stream::Stream { - self.matcher.fuzzy_match(haystack, needle) + async fn find(&mut self, pattern: &str) -> impl Iterator { + if pattern != self.last_pattern { + self.high_matcher.pattern.reparse( + 0, + pattern, + CaseMatching::Smart, + Normalization::Smart, + if pattern.len() > self.last_pattern.len() { + true + } else { + false + }, + ); + self.last_pattern = pattern.to_string(); + } + let snapshot = self.high_matcher.snapshot(); + snapshot + .matched_items(..snapshot.matched_item_count()) + .map(|item| { + // rematch with indices + let mut indices: Vec = Vec::new(); + let haystack = item.matcher_columns[0]; + self.low_matcher.fuzzy_indices( + haystack.slice(..), + Utf32String::from(pattern).slice(..), + &mut indices, + ) + }) } } @@ -73,36 +101,23 @@ fn walk_builder(path: &Path, n_threads: usize) -> WalkBuilder { builder } -async fn load_files(path: &Path) -> Vec { - let (tx, mut rx) = mpsc::channel(100); - let path = path.to_owned(); - - // Spawn a blocking task for the file walker - tokio::task::spawn_blocking(move || { - let walker = WalkBuilder::new(path) - .threads(*DEFAULT_NUM_THREADS) - .build_parallel(); - - walker.run(|| { - let tx = tx.clone(); - Box::new(move |result| { - if let Ok(entry) = result { - if entry.file_type().unwrap().is_file() { - // Send the path via the async channel - let _ = tx.blocking_send(entry.path().to_path_buf()); - } +async fn load_files(path: PathBuf, injector: Injector) { + let walker = WalkBuilder::new(path) + .threads(*DEFAULT_NUM_THREADS) + .build_parallel(); + + walker.run(|| { + let injector = injector.clone(); + Box::new(move |result| { + if let Ok(entry) = result { + if entry.file_type().unwrap().is_file() { + // Send the path via the async channel + let _ = injector.push(entry, |e, cols| { + cols[0] = e.path().to_string_lossy().into(); + }); } - ignore::WalkState::Continue - }) - }); + } + ignore::WalkState::Continue + }) }); - - // Collect the files asynchronously - let mut files = Vec::new(); - while let Some(file) = rx.recv().await { - files.push(file); - } - info!("Loaded {} files", files.len()); - - files } diff --git a/src/components/pickers/files.rs b/src/components/pickers/files.rs index 24f618b..51cb2a6 100644 --- a/src/components/pickers/files.rs +++ b/src/components/pickers/files.rs @@ -7,13 +7,13 @@ use crate::components::finders::{self, Finder}; use crate::components::pickers::Picker; use crate::components::previewers::{self, Previewer}; -pub struct FilePicker { - finder: finders::FileFinder, +pub struct FilePicker { + finder: finders::FileFinder, entries: Vec, previewer: previewers::FilePreviewer, } -impl FilePicker { +impl FilePicker { pub async fn new() -> Self { FilePicker { finder: finders::FileFinder::new().await, @@ -27,8 +27,8 @@ impl FilePicker { } } -impl Picker for FilePicker { - type F = finders::FileFinder; +impl Picker for FilePicker { + type F = finders::FileFinder; type P = previewers::FilePreviewer; async fn load_entries(&mut self, pattern: &str) -> Result<()> { diff --git a/src/components/previewers/files.rs b/src/components/previewers/files.rs index 5f0633c..6b2e2d3 100644 --- a/src/components/previewers/files.rs +++ b/src/components/previewers/files.rs @@ -1,5 +1,4 @@ use color_eyre::Result; -use lazy_static::lazy_static; use std::collections::HashMap; use std::io::BufRead; diff --git a/src/components/television.rs b/src/components/television.rs index cefd992..9288120 100644 --- a/src/components/television.rs +++ b/src/components/television.rs @@ -44,7 +44,6 @@ pub struct Television { action_tx: Option>, config: Config, channel: TvChannel, - channel_rx: Option current_pattern: String, current_pane: Pane, input: Input, @@ -71,6 +70,10 @@ impl Television { } } + fn find(&mut self, pattern: &str) { + self.channel.load_entries(pattern); + } + async fn sync_channel(&mut self, pattern: &str) -> Result<()> { self.channel.load_entries(pattern).await?; self.results_list.results = self.channel.entries().clone(); @@ -226,7 +229,8 @@ const DEFAULT_PREVIEW_GUTTER_SELECTED_FG: Color = Color::Rgb(255, 150, 150); impl Component for Television { fn register_action_handler(&mut self, tx: UnboundedSender) -> Result<()> { - self.action_tx = Some(tx); + self.action_tx = Some(tx.clone()); + self.channel.register_action_handler(tx.clone()); Ok(()) } @@ -271,7 +275,7 @@ impl Component for Television { let new_pattern = self.input.value().to_string(); if new_pattern != self.current_pattern { self.current_pattern = new_pattern.clone(); - self.sync_channel(&new_pattern).await?; + self.find(&new_pattern); } } _ => {}