Skip to content

Commit

Permalink
good progress
Browse files Browse the repository at this point in the history
  • Loading branch information
alexpasmantier committed Sep 26, 2024
1 parent 4e4f397 commit 12a9c5c
Show file tree
Hide file tree
Showing 8 changed files with 82 additions and 63 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
3 changes: 1 addition & 2 deletions src/components/finders.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use futures::Stream;
use rust_devicons::FileIcon;

mod env;
Expand Down Expand Up @@ -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<Item = Entry>;
async fn find(&mut self, pattern: &str) -> impl Iterator<Item = Entry>;
}
4 changes: 2 additions & 2 deletions src/components/finders/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ impl EnvVarFinder {
}

impl Finder for EnvVarFinder {
async fn find(&mut self, pattern: &str) -> impl Stream<Item = Entry> {
async fn find(&mut self, pattern: &str) -> impl Iterator<Item = Entry> {
let mut results: Vec<Entry> = Vec::new();
// try to get from cache
if let Some(entries) = self.cache.get(pattern) {
Expand Down Expand Up @@ -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()
}
}
115 changes: 65 additions & 50 deletions src/components/finders/files.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -20,8 +23,10 @@ use crate::{
pub struct FileFinder {
current_directory: PathBuf,
files: Vec<PathBuf>,
matcher: Option<Nucleo<_>>,
high_matcher: Nucleo<DirEntry>,
low_matcher: Matcher,
cache: HashMap<String, Vec<Entry>>,
last_pattern: String,
}

struct MatchItem {
Expand All @@ -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<Action>) -> 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<Item = Entry> {
self.matcher.fuzzy_match(haystack, needle)
async fn find(&mut self, pattern: &str) -> impl Iterator<Item = Entry> {
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<u32> = Vec::new();
let haystack = item.matcher_columns[0];
self.low_matcher.fuzzy_indices(
haystack.slice(..),
Utf32String::from(pattern).slice(..),
&mut indices,
)
})
}
}

Expand All @@ -73,36 +101,23 @@ fn walk_builder(path: &Path, n_threads: usize) -> WalkBuilder {
builder
}

async fn load_files(path: &Path) -> Vec<PathBuf> {
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<DirEntry>) {
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
}
10 changes: 5 additions & 5 deletions src/components/pickers/files.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T: Send + Sync + 'static> {
finder: finders::FileFinder<T>,
entries: Vec<finders::Entry>,
previewer: previewers::FilePreviewer,
}

impl FilePicker {
impl<T: Send + Sync + 'static> FilePicker<T> {
pub async fn new() -> Self {
FilePicker {
finder: finders::FileFinder::new().await,
Expand All @@ -27,8 +27,8 @@ impl FilePicker {
}
}

impl Picker for FilePicker {
type F = finders::FileFinder;
impl<T: Send + Sync + 'static> Picker for FilePicker<T> {
type F = finders::FileFinder<T>;
type P = previewers::FilePreviewer;

async fn load_entries(&mut self, pattern: &str) -> Result<()> {
Expand Down
1 change: 0 additions & 1 deletion src/components/previewers/files.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use color_eyre::Result;
use lazy_static::lazy_static;
use std::collections::HashMap;
use std::io::BufRead;

Expand Down
10 changes: 7 additions & 3 deletions src/components/television.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ pub struct Television {
action_tx: Option<UnboundedSender<Action>>,
config: Config,
channel: TvChannel,
channel_rx: Option<tokio::sync::mpsc::UnboundedReceiver<>
current_pattern: String,
current_pane: Pane,
input: Input,
Expand All @@ -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();
Expand Down Expand Up @@ -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<Action>) -> Result<()> {
self.action_tx = Some(tx);
self.action_tx = Some(tx.clone());
self.channel.register_action_handler(tx.clone());
Ok(())
}

Expand Down Expand Up @@ -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);
}
}
_ => {}
Expand Down

0 comments on commit 12a9c5c

Please sign in to comment.