From d65dcce743ab3b9ebac210aa2b5ee5e0c84a2f03 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Mon, 16 Mar 2026 10:42:02 -0700 Subject: specify version in all daemon atuin crates --- crates/atuin-daemon/Cargo.toml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'crates') diff --git a/crates/atuin-daemon/Cargo.toml b/crates/atuin-daemon/Cargo.toml index 1226a14c..b9d1d8fd 100644 --- a/crates/atuin-daemon/Cargo.toml +++ b/crates/atuin-daemon/Cargo.toml @@ -14,10 +14,10 @@ readme.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -atuin-client = { path = "../atuin-client" } -atuin-common = { path = "../atuin-common" } -atuin-dotfiles = { path = "../atuin-dotfiles" } -atuin-history = { path = "../atuin-history" } +atuin-client = { path = "../atuin-client", version = "18.13.2" } +atuin-common = { path = "../atuin-common", version = "18.13.2" } +atuin-dotfiles = { path = "../atuin-dotfiles", version = "18.13.2" } +atuin-history = { path = "../atuin-history", version = "18.13.2" } time = { workspace = true } uuid = { workspace = true } @@ -46,7 +46,6 @@ listenfd = "1.0.1" [dev-dependencies] tempfile = { workspace = true } -atuin-common = { path = "../atuin-common", version = "18.13.2" } [build-dependencies] protox = "0.9" -- cgit v1.3.1 From fecc8b6ee6ebf10054b3c5b76569bdd689e1798d Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Wed, 25 Feb 2026 19:49:39 -0800 Subject: Update readme --- crates/atuin-nucleo/README.md | 97 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) (limited to 'crates') diff --git a/crates/atuin-nucleo/README.md b/crates/atuin-nucleo/README.md index e617cfa0..d7c10735 100644 --- a/crates/atuin-nucleo/README.md +++ b/crates/atuin-nucleo/README.md @@ -1,4 +1,99 @@ -# Nucleo +# nucleo-ext + +This is a fork of [helix-editor/nucleo](https://github.com/helix-editor/nucleo) that includes filtering and custom scoring built into the matching loop. + +## Fork Changes + +### Filter Callback + +Filter items before fuzzy matching. Useful for category/facet filtering (e.g., filter by directory, host, session). + +```rust +use std::sync::Arc; + +// Filter to only include items where category == 1 +nucleo.set_filter(Some(Arc::new(|item: &MyItem| item.category == 1))); + +// Remove filter +nucleo.set_filter(None); +``` + +Filters are applied: +- During initial matching of new items +- When rescoring existing matches after pattern changes +- When resetting matches for empty patterns + +Setting a filter triggers a rescore on the next `tick()`. + +### Scorer Callback + +Compute custom scores after fuzzy matching. The scorer receives the item and the fuzzy score, and returns the final score used for sorting. + +```rust +use std::sync::Arc; + +// Use item priority as the score (ignoring fuzzy score) +nucleo.set_scorer(Some(Arc::new(|item: &MyItem, _fuzzy_score| { + item.priority +}))); + +// Combine fuzzy score with frecency +nucleo.set_scorer(Some(Arc::new(|item: &MyItem, fuzzy_score| { + fuzzy_score + item.frecency_score +}))); + +// Remove scorer (use raw fuzzy score) +nucleo.set_scorer(None); +``` + +The scorer output is stored in `Match::external_score` and used as the primary sort key. + +### Match Struct Changes + +The `Match` struct now has an additional field: + +```rust +pub struct Match { + pub score: u32, // Raw fuzzy match score + pub external_score: u32, // Scorer output (used for sorting) + pub idx: u32, // Item index +} +``` + +Results are sorted by `external_score` (descending), with tie-breakers on item length and index. + +### Use Case: Frecency Ranking + +This fork was created to support frecency-based ranking in [Atuin](https://github.com/atuinsh/atuin). The typical pattern is: + +1. Store metadata (frecency scores, categories) in a separate data structure +2. Use filter callback to exclude items that don't match the current context +3. Use scorer callback to combine fuzzy score with frecency at query time + +```rust +// External data store +let metadata: Arc> = /* ... */; + +// Filter: only show items used in current directory +let dir = current_dir.clone(); +let meta = metadata.clone(); +nucleo.set_filter(Some(Arc::new(move |cmd: &String| { + meta.get(cmd).map(|m| m.used_in_dir(&dir)).unwrap_or(false) +}))); + +// Scorer: combine fuzzy score with frecency +let meta = metadata.clone(); +nucleo.set_scorer(Some(Arc::new(move |cmd: &String, fuzzy_score| { + let frecency = meta.get(cmd).map(|m| m.frecency()).unwrap_or(0); + fuzzy_score + (frecency * 10) +}))); +``` + +--- + +The original Nucleo readme follows. + +--- `nucleo` is a highly performant fuzzy matcher written in Rust. It aims to fill the same use case as `fzf` and `skim`. Compared to `fzf` `nucleo` has a significantly faster matching algorithm. This mainly makes a difference when matching patterns with low selectivity on many items. An (unscientific) comparison is shown in the benchmark section below. -- cgit v1.3.1 From 7049c9b7878b2a3be013272469f94ee39d8a7e2c Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Wed, 25 Feb 2026 19:47:41 -0800 Subject: feat: Add custom filtering and scoring mechanisms --- crates/atuin-nucleo/src/lib.rs | 62 +++++++++- crates/atuin-nucleo/src/tests.rs | 242 +++++++++++++++++++++++++++++++++++++- crates/atuin-nucleo/src/worker.rs | 128 ++++++++++++++++++-- 3 files changed, 418 insertions(+), 14 deletions(-) (limited to 'crates') diff --git a/crates/atuin-nucleo/src/lib.rs b/crates/atuin-nucleo/src/lib.rs index 7ddb7407..5a500481 100644 --- a/crates/atuin-nucleo/src/lib.rs +++ b/crates/atuin-nucleo/src/lib.rs @@ -31,6 +31,15 @@ use std::sync::atomic::{self, AtomicBool, Ordering}; use std::sync::Arc; use std::time::Duration; +/// A filter predicate that determines whether an item should be included in matching. +/// Return `true` to include the item, `false` to skip it. +pub type Filter = Arc bool + Send + Sync>; + +/// A scorer callback that computes the final ranking score for an item. +/// Receives a reference to the item and its fuzzy match score. +/// Returns the combined/external score used for sorting results. +pub type Scorer = Arc u32 + Send + Sync>; + use parking_lot::Mutex; use rayon::ThreadPool; @@ -124,7 +133,13 @@ impl Injector { /// An [item](crate::Item) that was successfully matched by a [`Nucleo`] worker. #[derive(PartialEq, Eq, Debug, Clone, Copy)] pub struct Match { + /// The raw fuzzy match score from the matcher. pub score: u32, + /// The external/combined score used for sorting. + /// If no scorer callback is set, this equals `score`. + /// If a scorer callback is set, this is the value returned by the callback. + pub external_score: u32, + /// The index of the matched item in the item list. pub idx: u32, } @@ -290,6 +305,12 @@ pub struct Nucleo { /// Note that the matcher worker will only become aware of the new pattern /// after a call to [`tick`](Nucleo::tick). pub pattern: MultiPattern, + /// Optional filter predicate. Items where filter returns false are skipped. + filter: Option>, + /// Optional scorer callback. Returns combined score used for sorting. + scorer: Option>, + /// Flag indicating filter or scorer has changed and rescore is needed. + filter_scorer_changed: bool, } impl Nucleo { @@ -328,6 +349,9 @@ impl Nucleo { worker: Arc::new(Mutex::new(worker)), state: State::Init, notify, + filter: None, + scorer: None, + filter_scorer_changed: false, } } @@ -388,13 +412,46 @@ impl Nucleo { self.worker.lock().reverse_items(reverse_items) } + /// Set a filter predicate. Items where the filter returns `false` are + /// skipped during matching. This is applied before fuzzy matching, so + /// filtered items don't incur the cost of fuzzy matching. + /// + /// Setting a new filter triggers a rescore on the next [`tick`](Nucleo::tick). + /// + /// Pass `None` to remove the filter. + pub fn set_filter(&mut self, filter: Option>) { + self.filter = filter; + self.filter_scorer_changed = true; + } + + /// Set a scorer callback. The callback receives a reference to the item + /// and its fuzzy match score, and returns the combined score used for + /// sorting results. + /// + /// If no scorer is set, results are sorted by fuzzy match score. + /// + /// Setting a new scorer triggers a rescore on the next [`tick`](Nucleo::tick). + /// + /// Pass `None` to remove the scorer and use default fuzzy score sorting. + pub fn set_scorer(&mut self, scorer: Option>) { + self.scorer = scorer; + self.filter_scorer_changed = true; + } + /// The main way to interact with the matcher, this should be called /// regularly (for example each time a frame is rendered). To avoid /// excessive redraws this method will wait `timeout` milliseconds for the /// worker thread to finish. It is recommend to set the timeout to 10ms. pub fn tick(&mut self, timeout: u64) -> Status { self.should_notify.store(false, atomic::Ordering::Relaxed); - let status = self.pattern.status(); + let mut status = self.pattern.status(); + // If filter or scorer changed, treat as rescore + if self.filter_scorer_changed { + if status == pattern::Status::Unchanged { + status = pattern::Status::Rescore; + } + self.filter_scorer_changed = false; + } let canceled = status != pattern::Status::Unchanged || self.state.canceled(); let mut res = self.tick_inner(timeout, canceled, status); if !canceled { @@ -434,6 +491,9 @@ impl Nucleo { } if running { inner.pattern.clone_from(&self.pattern); + // Update filter and scorer in worker + inner.set_filter(self.filter.clone()); + inner.set_scorer(self.scorer.clone()); self.canceled.store(false, atomic::Ordering::Relaxed); if !canceled { self.should_notify.store(true, atomic::Ordering::Release); diff --git a/crates/atuin-nucleo/src/tests.rs b/crates/atuin-nucleo/src/tests.rs index 676c50df..96c4d99c 100644 --- a/crates/atuin-nucleo/src/tests.rs +++ b/crates/atuin-nucleo/src/tests.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use nucleo_matcher::Config; -use crate::Nucleo; +use crate::{pattern, Nucleo}; #[test] fn active_injector_count() { @@ -25,3 +25,243 @@ fn active_injector_count() { drop(injector3); assert_eq!(nucleo.active_injectors(), 0); } + +#[derive(Clone, Debug)] +struct TestItem { + text: String, + category: u32, + priority: u32, +} + +#[test] +fn filter_excludes_items() { + let mut nucleo: Nucleo = Nucleo::new(Config::DEFAULT, Arc::new(|| ()), Some(1), 1); + let injector = nucleo.injector(); + + // Add items with different categories + injector.push( + TestItem { + text: "apple".into(), + category: 1, + priority: 10, + }, + |item, cols| cols[0] = item.text.clone().into(), + ); + injector.push( + TestItem { + text: "apricot".into(), + category: 2, + priority: 20, + }, + |item, cols| cols[0] = item.text.clone().into(), + ); + injector.push( + TestItem { + text: "avocado".into(), + category: 1, + priority: 30, + }, + |item, cols| cols[0] = item.text.clone().into(), + ); + + // Search without filter - should get all 3 + nucleo.pattern.reparse( + 0, + "a", + pattern::CaseMatching::Ignore, + pattern::Normalization::Smart, + false, + ); + while nucleo.tick(10).running {} + assert_eq!(nucleo.snapshot().matched_item_count(), 3); + + // Set filter to only include category 1 + nucleo.set_filter(Some(Arc::new(|item: &TestItem| item.category == 1))); + + // Search again - should only get 2 items (apple, avocado) + while nucleo.tick(10).running {} + assert_eq!(nucleo.snapshot().matched_item_count(), 2); + + // Verify the items are correct + let items: Vec<_> = nucleo + .snapshot() + .matched_items(..) + .map(|i| i.data.text.clone()) + .collect(); + assert!(items.contains(&"apple".to_string())); + assert!(items.contains(&"avocado".to_string())); + assert!(!items.contains(&"apricot".to_string())); + + // Remove filter - should get all 3 again + nucleo.set_filter(None); + while nucleo.tick(10).running {} + assert_eq!(nucleo.snapshot().matched_item_count(), 3); +} + +#[test] +fn scorer_affects_sort_order() { + let mut nucleo: Nucleo = Nucleo::new(Config::DEFAULT, Arc::new(|| ()), Some(1), 1); + let injector = nucleo.injector(); + + // Add items with different priorities + injector.push( + TestItem { + text: "banana".into(), + category: 1, + priority: 10, + }, + |item, cols| cols[0] = item.text.clone().into(), + ); + injector.push( + TestItem { + text: "blueberry".into(), + category: 1, + priority: 100, + }, + |item, cols| cols[0] = item.text.clone().into(), + ); + injector.push( + TestItem { + text: "blackberry".into(), + category: 1, + priority: 50, + }, + |item, cols| cols[0] = item.text.clone().into(), + ); + + // Search without scorer - results sorted by fuzzy score + nucleo.pattern.reparse( + 0, + "b", + pattern::CaseMatching::Ignore, + pattern::Normalization::Smart, + false, + ); + while nucleo.tick(10).running {} + assert_eq!(nucleo.snapshot().matched_item_count(), 3); + + // Set scorer that uses priority as the score (ignoring fuzzy score) + nucleo.set_scorer(Some(Arc::new(|item: &TestItem, _fuzzy_score| { + item.priority + }))); + + // Search again - should be sorted by priority (high to low) + while nucleo.tick(10).running {} + let items: Vec<_> = nucleo + .snapshot() + .matched_items(..) + .map(|i| i.data.clone()) + .collect(); + assert_eq!(items.len(), 3); + assert_eq!(items[0].text, "blueberry"); // priority 100 + assert_eq!(items[1].text, "blackberry"); // priority 50 + assert_eq!(items[2].text, "banana"); // priority 10 + + // Verify external_score is set correctly + let matches = nucleo.snapshot().matches(); + assert_eq!(matches[0].external_score, 100); + assert_eq!(matches[1].external_score, 50); + assert_eq!(matches[2].external_score, 10); +} + +#[test] +fn filter_and_scorer_combined() { + let mut nucleo: Nucleo = Nucleo::new(Config::DEFAULT, Arc::new(|| ()), Some(1), 1); + let injector = nucleo.injector(); + + injector.push( + TestItem { + text: "cherry".into(), + category: 1, + priority: 10, + }, + |item, cols| cols[0] = item.text.clone().into(), + ); + injector.push( + TestItem { + text: "cranberry".into(), + category: 2, + priority: 100, + }, + |item, cols| cols[0] = item.text.clone().into(), + ); + injector.push( + TestItem { + text: "coconut".into(), + category: 1, + priority: 50, + }, + |item, cols| cols[0] = item.text.clone().into(), + ); + + // Set both filter (category 1) and scorer (priority) + nucleo.set_filter(Some(Arc::new(|item: &TestItem| item.category == 1))); + nucleo.set_scorer(Some(Arc::new(|item: &TestItem, _| item.priority))); + + nucleo.pattern.reparse( + 0, + "c", + pattern::CaseMatching::Ignore, + pattern::Normalization::Smart, + false, + ); + while nucleo.tick(10).running {} + + // Should have 2 items (cherry, coconut) sorted by priority + let items: Vec<_> = nucleo + .snapshot() + .matched_items(..) + .map(|i| i.data.clone()) + .collect(); + assert_eq!(items.len(), 2); + assert_eq!(items[0].text, "coconut"); // priority 50 + assert_eq!(items[1].text, "cherry"); // priority 10 +} + +#[test] +fn scorer_combines_with_fuzzy_score() { + let mut nucleo: Nucleo = Nucleo::new(Config::DEFAULT, Arc::new(|| ()), Some(1), 1); + let injector = nucleo.injector(); + + injector.push( + TestItem { + text: "date".into(), + category: 1, + priority: 100, + }, + |item, cols| cols[0] = item.text.clone().into(), + ); + injector.push( + TestItem { + text: "dragon fruit".into(), + category: 1, + priority: 10, + }, + |item, cols| cols[0] = item.text.clone().into(), + ); + + // Set scorer that combines fuzzy score with priority + nucleo.set_scorer(Some(Arc::new(|item: &TestItem, fuzzy_score| { + fuzzy_score + item.priority + }))); + + nucleo.pattern.reparse( + 0, + "d", + pattern::CaseMatching::Ignore, + pattern::Normalization::Smart, + false, + ); + while nucleo.tick(10).running {} + + // Both items match, verify that external_score includes priority boost + let matches = nucleo.snapshot().matches(); + assert_eq!(matches.len(), 2); + + // The raw fuzzy scores should be in Match.score + // The combined scores should be in Match.external_score + for m in matches { + let item = nucleo.snapshot().get_item(m.idx).unwrap(); + assert_eq!(m.external_score, m.score + item.data.priority); + } +} diff --git a/crates/atuin-nucleo/src/worker.rs b/crates/atuin-nucleo/src/worker.rs index f4077e6e..ddd546ad 100644 --- a/crates/atuin-nucleo/src/worker.rs +++ b/crates/atuin-nucleo/src/worker.rs @@ -9,7 +9,7 @@ use rayon::{prelude::*, ThreadPool}; use crate::par_sort::par_quicksort; use crate::pattern::{self, MultiPattern}; -use crate::{boxcar, Match}; +use crate::{boxcar, Filter, Match, Scorer}; struct Matchers(Box<[UnsafeCell]>); @@ -38,6 +38,8 @@ pub(crate) struct Worker { notify: Arc<(dyn Fn() + Sync + Send)>, pub(crate) items: Arc>, in_flight: Vec, + pub(crate) filter: Option>, + pub(crate) scorer: Option>, } impl Worker { @@ -56,6 +58,14 @@ impl Worker { self.reverse_items = reverse_items; } + pub(crate) fn set_filter(&mut self, filter: Option>) { + self.filter = filter; + } + + pub(crate) fn set_scorer(&mut self, scorer: Option>) { + self.scorer = scorer; + } + pub(crate) fn new( worker_threads: Option, config: Config, @@ -87,6 +97,8 @@ impl Worker { notify, items: Arc::new(boxcar::Vec::with_capacity(2 * 1024, cols)), in_flight: Vec::with_capacity(64), + filter: None, + scorer: None, }; (pool, worker) } @@ -99,8 +111,22 @@ impl Worker { let Some(item) = self.items.get(idx) else { return true; }; + // Apply filter if set + if let Some(ref filter) = self.filter { + if !filter(item.data) { + return false; // Item is ready but filtered out + } + } if let Some(score) = pattern.score(item.matcher_columns, matchers.get()) { - self.matches.push(Match { score, idx }); + let external_score = match &self.scorer { + Some(scorer) => scorer(item.data, score), + None => score, + }; + self.matches.push(Match { + score, + external_score, + idx, + }); }; false }); @@ -114,20 +140,45 @@ impl Worker { unmatched.fetch_add(1, atomic::Ordering::Relaxed); return Match { score: 0, + external_score: 0, idx: u32::MAX, }; }; if self.canceled.load(atomic::Ordering::Relaxed) { - return Match { score: 0, idx }; + return Match { + score: 0, + external_score: 0, + idx, + }; + } + // Apply filter if set + if let Some(ref filter) = self.filter { + if !filter(item.data) { + unmatched.fetch_add(1, atomic::Ordering::Relaxed); + return Match { + score: 0, + external_score: 0, + idx: u32::MAX, + }; + } } let Some(score) = pattern.score(item.matcher_columns, matchers.get()) else { unmatched.fetch_add(1, atomic::Ordering::Relaxed); return Match { score: 0, + external_score: 0, idx: u32::MAX, }; }; - Match { score, idx } + let external_score = match &self.scorer { + Some(scorer) => scorer(item.data, score), + None => score, + }; + Match { + score, + external_score, + idx, + } }); self.matches.par_extend(items); self.last_snapshot = end; @@ -151,11 +202,23 @@ impl Worker { if new_snapshot.end() != self.last_snapshot { let end = new_snapshot.end(); let items = new_snapshot.filter_map(|(idx, item)| { - if item.is_none() { - self.in_flight.push(idx); - return None; + let item = item?; + // Apply filter if set + if let Some(ref filter) = self.filter { + if !filter(item.data) { + return None; + } + } + // For empty pattern, apply scorer with score=0 if set + let external_score = match &self.scorer { + Some(scorer) => scorer(item.data, 0), + None => 0, }; - Some(Match { score: 0, idx }) + Some(Match { + score: 0, + external_score, + idx, + }) }); self.matches.extend(items); self.last_snapshot = end; @@ -205,11 +268,26 @@ impl Worker { } // safety: in-flight items are never added to the matches let item = self.items.get_unchecked(match_.idx); + // Apply filter if set + if let Some(ref filter) = self.filter { + if !filter(item.data) { + unmatched.fetch_add(1, atomic::Ordering::Relaxed); + match_.score = 0; + match_.external_score = 0; + match_.idx = u32::MAX; + return; + } + } if let Some(score) = pattern.score(item.matcher_columns, matchers.get()) { match_.score = score; + match_.external_score = match &self.scorer { + Some(scorer) => scorer(item.data, score), + None => score, + }; } else { unmatched.fetch_add(1, atomic::Ordering::Relaxed); match_.score = 0; + match_.external_score = 0; match_.idx = u32::MAX; } }); @@ -234,8 +312,9 @@ impl Worker { par_quicksort( &mut self.matches, |match1, match2| { - if match1.score != match2.score { - return match1.score > match2.score; + // Primary sort: external_score (used for frecency/custom ranking) + if match1.external_score != match2.external_score { + return match1.external_score > match2.external_score; } if match1.idx == u32::MAX { return false; @@ -293,8 +372,33 @@ impl Worker { fn reset_matches(&mut self) { self.matches.clear(); - self.matches - .extend((0..self.last_snapshot).map(|idx| Match { score: 0, idx })); + // When resetting, apply filter if set + if let Some(ref filter) = self.filter { + for idx in 0..self.last_snapshot { + // Items up to last_snapshot should be initialized + if let Some(item) = self.items.get(idx) { + if filter(item.data) { + let external_score = match &self.scorer { + Some(scorer) => scorer(item.data, 0), + None => 0, + }; + self.matches.push(Match { + score: 0, + external_score, + idx, + }); + } + } + } + } else { + // No filter - add all items + self.matches + .extend((0..self.last_snapshot).map(|idx| Match { + score: 0, + external_score: 0, + idx, + })); + } // there are usually only very few in flight items (one for each writer) self.remove_in_flight_matches(); } -- cgit v1.3.1 From 0f67f59e585836145e436310caabb338b12062a7 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Mon, 16 Mar 2026 15:49:54 -0700 Subject: vendor nucleo fork into atuin workspace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rename crates (nucleo → atuin-nucleo, nucleo-matcher → atuin-nucleo-matcher), add to workspace members and dependencies, update all import paths, remove vendored CI workflow, and suppress upstream clippy warnings. format codespell fixes clippy clappy --- .codespellrc | 2 +- Cargo.lock | 248 ++++++++++----------- Cargo.toml | 6 +- crates/atuin-daemon/Cargo.toml | 2 +- crates/atuin-daemon/src/search/index.rs | 8 +- crates/atuin-nucleo/.github/workflows/ci.yml | 83 ------- crates/atuin-nucleo/Cargo.toml | 15 +- crates/atuin-nucleo/bench/Cargo.toml | 6 +- crates/atuin-nucleo/bench/src/main.rs | 4 +- crates/atuin-nucleo/matcher/Cargo.toml | 4 +- .../matcher/fuzz/fuzz_targets/fuzz_target_1.rs | 2 +- crates/atuin-nucleo/matcher/src/chars/normalize.rs | 2 +- crates/atuin-nucleo/matcher/src/fuzzy_greedy.rs | 2 +- crates/atuin-nucleo/matcher/src/fuzzy_optimal.rs | 6 +- crates/atuin-nucleo/matcher/src/lib.rs | 18 +- crates/atuin-nucleo/matcher/src/tests.rs | 2 +- crates/atuin-nucleo/matcher/src/utf32_str.rs | 8 +- crates/atuin-nucleo/src/boxcar.rs | 2 +- crates/atuin-nucleo/src/lib.rs | 16 +- crates/atuin-nucleo/src/pattern.rs | 8 +- crates/atuin-nucleo/src/pattern/tests.rs | 2 +- crates/atuin-nucleo/src/tests.rs | 2 +- crates/atuin-nucleo/src/worker.rs | 12 +- crates/atuin/Cargo.toml | 2 +- .../src/command/client/search/engines/daemon.rs | 4 +- 25 files changed, 184 insertions(+), 282 deletions(-) delete mode 100644 crates/atuin-nucleo/.github/workflows/ci.yml (limited to 'crates') diff --git a/.codespellrc b/.codespellrc index c2a41811..aede4b14 100644 --- a/.codespellrc +++ b/.codespellrc @@ -3,5 +3,5 @@ skip = .git*,*.lock,.codespellrc,CODE_OF_CONDUCT.md,CONTRIBUTORS check-hidden = true # ignore-regex = -ignore-words-list = crate,ratatui,inbetween,iterm +ignore-words-list = crate,ratatui,inbetween,iterm,fo,brunch diff --git a/Cargo.lock b/Cargo.lock index 4a20f6af..62b40c54 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -56,9 +56,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.21" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -71,15 +71,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] @@ -229,6 +229,7 @@ dependencies = [ "atuin-hex", "atuin-history", "atuin-kv", + "atuin-nucleo-matcher", "atuin-scripts", "atuin-server", "atuin-server-database", @@ -249,7 +250,6 @@ dependencies = [ "itertools", "log", "norm", - "nucleo-matcher", "open", "ratatui", "regex", @@ -385,12 +385,12 @@ dependencies = [ "atuin-common", "atuin-dotfiles", "atuin-history", + "atuin-nucleo", "dashmap", "eyre", "hyper-util", "lasso", "listenfd", - "nucleo", "prost", "prost-types", "protox", @@ -465,6 +465,33 @@ dependencies = [ "typed-builder", ] +[[package]] +name = "atuin-nucleo" +version = "0.6.0" +dependencies = [ + "atuin-nucleo-matcher", + "parking_lot", + "rayon", +] + +[[package]] +name = "atuin-nucleo-bench" +version = "0.1.0" +dependencies = [ + "atuin-nucleo", + "brunch", + "fuzzy-matcher", + "walkdir", +] + +[[package]] +name = "atuin-nucleo-matcher" +version = "0.3.1" +dependencies = [ + "memchr", + "unicode-segmentation", +] + [[package]] name = "atuin-scripts" version = "18.13.2" @@ -688,6 +715,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "brunch" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3932d710d985d35c7b08e7e439a6ac8607aa8f619d373eb1f808578cd3cd56e5" +dependencies = [ + "dactyl", + "unicode-width 0.1.14", +] + [[package]] name = "bumpalo" version = "3.20.2" @@ -735,9 +772,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.56" +version = "1.2.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" dependencies = [ "find-msvc-tools", "shlex", @@ -797,9 +834,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.60" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" dependencies = [ "clap_builder", "clap_derive", @@ -807,9 +844,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.60" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstream", "anstyle", @@ -820,18 +857,18 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.66" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c757a3b7e39161a4e56f9365141ada2a6c915a8622c408ab6bb4b5d047371031" +checksum = "19c9f1dde76b736e3681f28cec9d5a61299cbaae0fce80a68e43724ad56031eb" dependencies = [ "clap", ] [[package]] name = "clap_complete_nushell" -version = "4.5.10" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685bc86fd34b7467e0532a4f8435ab107960d69a243785ef0275e571b35b641a" +checksum = "fbb9e9715d29a754b468591be588f6b926f5b0a1eb6a8b62acabeb66ff84d897" dependencies = [ "clap", "clap_complete", @@ -839,9 +876,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.55" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" dependencies = [ "heck", "proc-macro2", @@ -851,9 +888,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "clipboard-win" @@ -866,9 +903,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "colored" @@ -921,9 +958,9 @@ checksum = "baf0a07a401f374238ab8e2f11a104d2851bf9ce711ec69804834de8af45c7af" [[package]] name = "config" -version = "0.15.19" +version = "0.15.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b30fa8254caad766fc03cb0ccae691e14bf3bd72bfff27f72802ce729551b3d6" +checksum = "4fe5feec195269515c4722937cd7ffcfe7b4205d18d2e6577b7223ecb159ab00" dependencies = [ "pathdiff", "serde_core", @@ -933,13 +970,12 @@ dependencies = [ [[package]] name = "console" -version = "0.16.2" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03e45a4a8926227e4197636ba97a9fc9b00477e9f4bd711395687c5f0734bec4" +checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87" dependencies = [ "encode_unicode", "libc", - "once_cell", "unicode-width 0.2.2", "windows-sys 0.61.2", ] @@ -1150,6 +1186,12 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "dactyl" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ecad1ab94b1336be6cff409436ad9ceedb0afd52a85d54132189c2c3babb049" + [[package]] name = "daemonize" version = "0.5.0" @@ -1159,38 +1201,14 @@ dependencies = [ "libc", ] -[[package]] -name = "darling" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" -dependencies = [ - "darling_core 0.21.3", - "darling_macro 0.21.3", -] - [[package]] name = "darling" version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" dependencies = [ - "darling_core 0.23.0", - "darling_macro 0.23.0", -] - -[[package]] -name = "darling_core" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.117", + "darling_core", + "darling_macro", ] [[package]] @@ -1206,24 +1224,13 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "darling_macro" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" -dependencies = [ - "darling_core 0.21.3", - "quote", - "syn 2.0.117", -] - [[package]] name = "darling_macro" version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ - "darling_core 0.23.0", + "darling_core", "quote", "syn 2.0.117", ] @@ -2261,9 +2268,9 @@ dependencies = [ [[package]] name = "image" -version = "0.25.9" +version = "0.25.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" dependencies = [ "bytemuck", "byteorder-lite", @@ -2355,11 +2362,11 @@ dependencies = [ [[package]] name = "instability" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357b7205c6cd18dd2c86ed312d1e70add149aea98e7ef72b9fdf0270e555c11d" +checksum = "5eb2d60ef19920a3a9193c3e371f726ec1dafc045dac788d0fb3704272458971" dependencies = [ - "darling 0.23.0", + "darling", "indoc", "proc-macro2", "quote", @@ -2493,9 +2500,9 @@ dependencies = [ [[package]] name = "kasuari" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fe90c1150662e858c7d5f945089b7517b0a80d8bf7ba4b1b5ffc984e7230a5b" +checksum = "bde5057d6143cc94e861d90f591b9303d6716c6b9602309150bd068853c10899" dependencies = [ "hashbrown 0.16.1", "portable-atomic", @@ -2731,6 +2738,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15" +[[package]] +name = "memo-map" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d1115007560874e373613744c6fba374c17688327a71c1476d1a5954cc857b" + [[package]] name = "memoffset" version = "0.6.5" @@ -2819,10 +2832,11 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "minijinja" -version = "2.17.1" +version = "2.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea5ea1e90055f200af6b8e52a4a34e05e77e7fee953a9fb40c631efdc43cab1" +checksum = "328251e58ad8e415be6198888fc207502727dc77945806421ab34f35bf012e7d" dependencies = [ + "memo-map", "serde", ] @@ -2862,9 +2876,9 @@ dependencies = [ [[package]] name = "moxcms" -version = "0.7.11" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" dependencies = [ "num-traits", "pxfm", @@ -2977,25 +2991,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "nucleo" -version = "0.5.0" -source = "git+https://github.com/atuinsh/nucleo-ext.git?rev=74bd786#74bd786e98f7c88d68f967855d6f57b3ac2d09ef" -dependencies = [ - "nucleo-matcher", - "parking_lot", - "rayon", -] - -[[package]] -name = "nucleo-matcher" -version = "0.3.1" -source = "git+https://github.com/atuinsh/nucleo-ext.git?rev=74bd786#74bd786e98f7c88d68f967855d6f57b3ac2d09ef" -dependencies = [ - "memchr", - "unicode-segmentation", -] - [[package]] name = "num-bigint-dig" version = "0.8.6" @@ -3152,9 +3147,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "once_cell_polyfill" @@ -4313,9 +4308,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ "windows-sys 0.61.2", ] @@ -4466,9 +4461,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.17.0" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "381b283ce7bc6b476d903296fb59d0d36633652b633b27f64db4fb46dcbfc3b9" +checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f" dependencies = [ "base64", "chrono", @@ -4485,11 +4480,11 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.17.0" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6d4e30573c8cb306ed6ab1dca8423eec9a463ea0e155f45399455e0368b27e0" +checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65" dependencies = [ - "darling 0.21.3", + "darling", "proc-macro2", "quote", "syn 2.0.117", @@ -5026,9 +5021,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.26.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", "getrandom 0.4.2", @@ -5179,9 +5174,9 @@ dependencies = [ [[package]] name = "tiff" -version = "0.10.3" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f" +checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" dependencies = [ "fax", "flate2", @@ -5253,9 +5248,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" dependencies = [ "tinyvec_macros", ] @@ -5330,26 +5325,17 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.12+spec-1.1.0" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" +checksum = "399b1124a3c9e16766831c6bba21e50192572cdd98706ea114f9502509686ffc" dependencies = [ "serde_core", "serde_spanned", - "toml_datetime 0.7.5+spec-1.1.0", + "toml_datetime", "toml_parser", "winnow", ] -[[package]] -name = "toml_datetime" -version = "0.7.5+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" -dependencies = [ - "serde_core", -] - [[package]] name = "toml_datetime" version = "1.0.0+spec-1.1.0" @@ -5366,7 +5352,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2" dependencies = [ "indexmap 2.13.0", - "toml_datetime 1.0.0+spec-1.1.0", + "toml_datetime", "toml_parser", "toml_writer", "winnow", @@ -5584,9 +5570,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.22" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" dependencies = [ "matchers", "nu-ansi-term", @@ -6946,15 +6932,15 @@ checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" [[package]] name = "zune-core" -version = "0.4.12" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" +checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" [[package]] name = "zune-jpeg" -version = "0.4.21" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" +checksum = "ec5f41c76397b7da451efd19915684f727d7e1d516384ca6bd0ec43ec94de23c" dependencies = [ "zune-core", ] diff --git a/Cargo.toml b/Cargo.toml index 0f4203b0..65bfd48d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [workspace] -members = ["crates/*"] +members = ["crates/*", "crates/atuin-nucleo/matcher", "crates/atuin-nucleo/bench"] resolver = "2" -exclude = ["ui/backend"] +exclude = ["ui/backend", "crates/atuin-nucleo/matcher/fuzz"] [workspace.package] version = "18.13.2" @@ -26,6 +26,8 @@ atuin-server = { path = "crates/atuin-server", version = "18.13.2" } atuin-server-database = { path = "crates/atuin-server-database", version = "18.13.2" } atuin-server-postgres = { path = "crates/atuin-server-postgres", version = "18.13.2" } atuin-server-sqlite = { path = "crates/atuin-server-sqlite", version = "18.13.2" } +atuin-nucleo = { path = "crates/atuin-nucleo", version = "0.6.0" } +atuin-nucleo-matcher = { path = "crates/atuin-nucleo/matcher", version = "0.3.1" } base64 = "0.22" crossterm = "0.29.0" log = "0.4" diff --git a/crates/atuin-daemon/Cargo.toml b/crates/atuin-daemon/Cargo.toml index b9d1d8fd..dd92df8b 100644 --- a/crates/atuin-daemon/Cargo.toml +++ b/crates/atuin-daemon/Cargo.toml @@ -38,7 +38,7 @@ tokio-stream = { version = "0.1.14", features = ["net"] } hyper-util = "0.1" rand.workspace = true -nucleo = { git = "https://github.com/atuinsh/nucleo-ext.git", rev="74bd786" } +atuin-nucleo = { workspace = true } [target.'cfg(target_os = "linux")'.dependencies] diff --git a/crates/atuin-daemon/src/search/index.rs b/crates/atuin-daemon/src/search/index.rs index 1445871e..3328c5b5 100644 --- a/crates/atuin-daemon/src/search/index.rs +++ b/crates/atuin-daemon/src/search/index.rs @@ -14,9 +14,9 @@ use std::{ use atuin_client::history::History; use atuin_client::settings::Search; +use atuin_nucleo::{Injector, Nucleo, pattern}; use dashmap::DashMap; use lasso::{Spur, ThreadedRodeo}; -use nucleo::{Injector, Nucleo, pattern}; use time::OffsetDateTime; use tokio::sync::RwLock; use tracing::{Level, instrument}; @@ -269,7 +269,7 @@ pub struct SearchIndex { impl SearchIndex { /// Create a new empty search index. pub fn new() -> Self { - let nucleo_config = nucleo::Config::DEFAULT; + let nucleo_config = atuin_nucleo::Config::DEFAULT; // Single column for command text let nucleo = Nucleo::::new(nucleo_config, Arc::new(|| {}), None, 1); let injector = nucleo.injector(); @@ -417,7 +417,7 @@ impl SearchIndex { } /// Build filter predicate for the given mode. - fn build_filter(&self, mode: &IndexFilterMode) -> Option> { + fn build_filter(&self, mode: &IndexFilterMode) -> Option> { // For Global mode, no filter needed if matches!(mode, IndexFilterMode::Global) { return None; @@ -455,7 +455,7 @@ impl SearchIndex { /// Build scorer from precomputed frecency map. /// /// Returns None if frecency map is not available (search still works, just without frecency ranking). - fn build_scorer(frecency_map: Option) -> Option> { + fn build_scorer(frecency_map: Option) -> Option> { let map = frecency_map?; Some(Arc::new(move |cmd: &String, fuzzy_score: u32| { // HashMap, _>::get accepts &str via Borrow trait diff --git a/crates/atuin-nucleo/.github/workflows/ci.yml b/crates/atuin-nucleo/.github/workflows/ci.yml deleted file mode 100644 index e478b6ae..00000000 --- a/crates/atuin-nucleo/.github/workflows/ci.yml +++ /dev/null @@ -1,83 +0,0 @@ -name: CI -on: - pull_request: - push: - branches: - - master - -jobs: - check-msrv: - name: Check - strategy: - matrix: - toolchain: - - "1.65" - - stable - runs-on: ubuntu-latest - steps: - - name: Checkout sources - uses: actions/checkout@v4 - - - name: Install toolchain - uses: dtolnay/rust-toolchain@master - with: - toolchain: ${{ matrix.toolchain}} - - - uses: Swatinem/rust-cache@v2 - - - name: Run cargo check - run: cargo check - - name: Run cargo check withoult default features - run: cargo check --no-default-features - - test: - name: Test - runs-on: ubuntu-latest - steps: - - name: Checkout sources - uses: actions/checkout@v4 - - - name: Install stable toolchain - uses: dtolnay/rust-toolchain@stable - - - uses: Swatinem/rust-cache@v2 - - - name: Run cargo test - run: cargo test --workspace - - lints: - name: Lints - runs-on: ubuntu-latest - steps: - - name: Checkout sources - uses: actions/checkout@v4 - - - name: Install stable toolchain - uses: dtolnay/rust-toolchain@stable - with: - components: rustfmt, clippy - - - uses: Swatinem/rust-cache@v2 - - - name: Run cargo fmt - run: cargo fmt --all --check - - - name: Run cargo clippy - run: cargo clippy --workspace --all-targets -- -D warnings - - name: Run cargo clippy withoult default features - run: cargo clippy --workspace --all-targets --no-default-features -- -D warnings - - - name: Run cargo doc - run: cargo doc --no-deps --workspace --document-private-items - env: - RUSTDOCFLAGS: -D warnings - - typos: - name: Typos - runs-on: ubuntu-latest - steps: - - name: Checkout sources - uses: actions/checkout@v4 - - - name: Run typos - uses: crate-ci/typos@v1.16.11 diff --git a/crates/atuin-nucleo/Cargo.toml b/crates/atuin-nucleo/Cargo.toml index eb6315fb..cd53e578 100644 --- a/crates/atuin-nucleo/Cargo.toml +++ b/crates/atuin-nucleo/Cargo.toml @@ -1,20 +1,17 @@ [package] -name = "nucleo" -description = "plug and play high performance fuzzy matcher" -authors = ["Pascal Kuthe "] -version = "0.5.0" +name = "atuin-nucleo" +description = "A fork of helix-editor/nucleo with filtering and custom scoring for Atuin" +authors = ["Pascal Kuthe ", "Michelle Tilley "] +version = "0.6.0" edition = "2021" license = "MPL-2.0" -repository = "https://github.com/helix-editor/nucleo" +repository = "https://github.com/atuinsh/atuin" readme = "README.md" exclude = ["/typos.toml", "/tarpaulin.toml"] [lib] [dependencies] -nucleo-matcher = { version = "0.3.1", path = "matcher" } +atuin-nucleo-matcher = { version = "0.3.1", path = "matcher" } parking_lot = { version = "0.12.1", features = ["send_guard", "arc_lock"] } rayon = "1.7.0" - -[workspace] -members = ["matcher", "bench"] diff --git a/crates/atuin-nucleo/bench/Cargo.toml b/crates/atuin-nucleo/bench/Cargo.toml index 0dfb81d5..d71734e1 100644 --- a/crates/atuin-nucleo/bench/Cargo.toml +++ b/crates/atuin-nucleo/bench/Cargo.toml @@ -1,12 +1,10 @@ [package] -name = "benches" +name = "atuin-nucleo-bench" version = "0.1.0" edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] -nucleo = { version = "*", path = "../" } +atuin-nucleo = { version = "*", path = "../" } brunch = "0.5.0" fuzzy-matcher = "0.3.7" walkdir = "2" diff --git a/crates/atuin-nucleo/bench/src/main.rs b/crates/atuin-nucleo/bench/src/main.rs index bc77b03d..43e2ed26 100644 --- a/crates/atuin-nucleo/bench/src/main.rs +++ b/crates/atuin-nucleo/bench/src/main.rs @@ -2,9 +2,9 @@ use std::hint::black_box; use std::path::PathBuf; use std::process::Command; +use atuin_nucleo::{Utf32Str, Utf32String}; use brunch::{Bench, Benches}; use fuzzy_matcher::FuzzyMatcher; -use nucleo::{Utf32Str, Utf32String}; fn bench_dir() -> PathBuf { std::env::var_os("BENCHMARK_DIR") @@ -43,7 +43,7 @@ fn main() { Some((path.as_str().into(), path)) }) .unzip(); - let mut nucleo = nucleo::Matcher::new(nucleo::Config::DEFAULT.match_paths()); + let mut nucleo = atuin_nucleo::Matcher::new(atuin_nucleo::Config::DEFAULT.match_paths()); let skim = fuzzy_matcher::skim::SkimMatcherV2::default(); // TODO: unicode? diff --git a/crates/atuin-nucleo/matcher/Cargo.toml b/crates/atuin-nucleo/matcher/Cargo.toml index 4b90ddbb..feb10310 100644 --- a/crates/atuin-nucleo/matcher/Cargo.toml +++ b/crates/atuin-nucleo/matcher/Cargo.toml @@ -1,11 +1,11 @@ [package] -name = "nucleo-matcher" +name = "atuin-nucleo-matcher" description = "plug and play high performance fuzzy matcher" authors = ["Pascal Kuthe "] version = "0.3.1" edition = "2021" license = "MPL-2.0" -repository = "https://github.com/helix-editor/nucleo" +repository = "https://github.com/atuinsh/atuin" readme = "../README.md" [dependencies] diff --git a/crates/atuin-nucleo/matcher/fuzz/fuzz_targets/fuzz_target_1.rs b/crates/atuin-nucleo/matcher/fuzz/fuzz_targets/fuzz_target_1.rs index d9df7d36..00940d58 100644 --- a/crates/atuin-nucleo/matcher/fuzz/fuzz_targets/fuzz_target_1.rs +++ b/crates/atuin-nucleo/matcher/fuzz/fuzz_targets/fuzz_target_1.rs @@ -64,7 +64,7 @@ fuzz_target!(|data: Input<'_>| { (Some(greedy), Some(optimal)) => { assert!( greedy <= optimal, - "optimal score must be atleast the same as greedy score {greedy} {optimal}" + "optimal score must be at least the same as greedy score {greedy} {optimal}" ); if indices_greedy == indices_optimal { assert_eq!( diff --git a/crates/atuin-nucleo/matcher/src/chars/normalize.rs b/crates/atuin-nucleo/matcher/src/chars/normalize.rs index 3de501aa..7e3b3b17 100644 --- a/crates/atuin-nucleo/matcher/src/chars/normalize.rs +++ b/crates/atuin-nucleo/matcher/src/chars/normalize.rs @@ -19,7 +19,7 @@ /// /// # Example /// ``` -/// # use nucleo_matcher::chars::normalize; +/// # use atuin_nucleo_matcher::chars::normalize; /// assert_eq!(normalize('ä'), 'a'); /// assert_eq!(normalize('Æ'), 'Æ'); /// assert_eq!(normalize('ữ'), 'u'); diff --git a/crates/atuin-nucleo/matcher/src/fuzzy_greedy.rs b/crates/atuin-nucleo/matcher/src/fuzzy_greedy.rs index 8215bf31..386d289c 100644 --- a/crates/atuin-nucleo/matcher/src/fuzzy_greedy.rs +++ b/crates/atuin-nucleo/matcher/src/fuzzy_greedy.rs @@ -2,7 +2,7 @@ use crate::chars::Char; use crate::Matcher; impl Matcher { - /// greedy fallback algorithm, much faster (linear time) but reported scores/indicies + /// greedy fallback algorithm, much faster (linear time) but reported scores/indices /// might not be the best match pub(crate) fn fuzzy_match_greedy_, N: Char>( &mut self, diff --git a/crates/atuin-nucleo/matcher/src/fuzzy_optimal.rs b/crates/atuin-nucleo/matcher/src/fuzzy_optimal.rs index 5d53ecfb..1bcebe2c 100644 --- a/crates/atuin-nucleo/matcher/src/fuzzy_optimal.rs +++ b/crates/atuin-nucleo/matcher/src/fuzzy_optimal.rs @@ -49,7 +49,7 @@ impl Matcher { .iter() .enumerate() .max_by_key(|(_, cell)| cell.score) - .expect("there must be atleast one match"); + .expect("there must be at least one match"); if INDICES { matrix.reconstruct_optimal_path(match_end as u16, indices, matrix_len, start as u32); } @@ -60,7 +60,7 @@ impl Matcher { const UNMATCHED: ScoreCell = ScoreCell { score: 0, // if matched is true then the consecutive bonus - // is always atleast BONUS_CONSECUTIVE so + // is always at least BONUS_CONSECUTIVE so // this constant can never occur naturally consecutive_bonus: 0, matched: true, @@ -145,7 +145,7 @@ impl MatcherDataView<'_, H> { (needle_char, row_start) = next; } else if !matched { *row_start = i; - // we have atleast one match + // we have at least one match matched = true; } } diff --git a/crates/atuin-nucleo/matcher/src/lib.rs b/crates/atuin-nucleo/matcher/src/lib.rs index 3e8874c5..9ae4b665 100644 --- a/crates/atuin-nucleo/matcher/src/lib.rs +++ b/crates/atuin-nucleo/matcher/src/lib.rs @@ -1,5 +1,7 @@ +#![allow(clippy::needless_return, mismatched_lifetime_syntaxes)] + /*! -`nucleo_matcher` is a low level crate that contains the matcher implementation +`atuin_nucleo_matcher` is a low level crate that contains the matcher implementation used by the high level `nucleo` crate. **NOTE**: If you are building an fzf-like interactive fuzzy finder that is @@ -20,8 +22,8 @@ can contain special characters to control what kind of match is performed (see [`AtomKind`](crate::pattern::AtomKind)). ``` -# use nucleo_matcher::{Matcher, Config}; -# use nucleo_matcher::pattern::{Pattern, Normalization, CaseMatching}; +# use atuin_nucleo_matcher::{Matcher, Config}; +# use atuin_nucleo_matcher::pattern::{Pattern, Normalization, CaseMatching}; let paths = ["foo/bar", "bar/foo", "foobar"]; let mut matcher = Matcher::new(Config::DEFAULT.match_paths()); let matches = Pattern::parse("foo bar", CaseMatching::Ignore, Normalization::Smart).match_list(paths, &mut matcher); @@ -34,8 +36,8 @@ If the pattern should be matched literally (without this special parsing) [`Pattern::new`](pattern::Pattern::new) can be used instead. ``` -# use nucleo_matcher::{Matcher, Config}; -# use nucleo_matcher::pattern::{Pattern, CaseMatching, AtomKind, Normalization}; +# use atuin_nucleo_matcher::{Matcher, Config}; +# use atuin_nucleo_matcher::pattern::{Pattern, CaseMatching, AtomKind, Normalization}; let paths = ["foo/bar", "bar/foo", "foobar"]; let mut matcher = Matcher::new(Config::DEFAULT.match_paths()); let matches = Pattern::new("foo bar", CaseMatching::Ignore, Normalization::Smart, AtomKind::Fuzzy).match_list(paths, &mut matcher); @@ -49,7 +51,7 @@ Word segmentation is performed automatically on any unescaped character for whic This is relevant, for instance, with non-english keyboard input. ``` -# use nucleo_matcher::pattern::{Atom, Pattern, Normalization, CaseMatching}; +# use atuin_nucleo_matcher::pattern::{Atom, Pattern, Normalization, CaseMatching}; assert_eq!( // double-width 'Ideographic Space', i.e. `'\u{3000}'` Pattern::parse("ほげ ふが", CaseMatching::Smart, Normalization::Smart).atoms, @@ -63,8 +65,8 @@ assert_eq!( If word segmentation is also not desired, a single `Atom` can be constructed directly. ``` -# use nucleo_matcher::{Matcher, Config}; -# use nucleo_matcher::pattern::{Pattern, Atom, CaseMatching, Normalization, AtomKind}; +# use atuin_nucleo_matcher::{Matcher, Config}; +# use atuin_nucleo_matcher::pattern::{Pattern, Atom, CaseMatching, Normalization, AtomKind}; let paths = ["foobar", "foo bar"]; let mut matcher = Matcher::new(Config::DEFAULT); let matches = Atom::new("foo bar", CaseMatching::Ignore, Normalization::Smart, AtomKind::Fuzzy, false).match_list(paths, &mut matcher); diff --git a/crates/atuin-nucleo/matcher/src/tests.rs b/crates/atuin-nucleo/matcher/src/tests.rs index 32a02403..a883c6ba 100644 --- a/crates/atuin-nucleo/matcher/src/tests.rs +++ b/crates/atuin-nucleo/matcher/src/tests.rs @@ -631,7 +631,7 @@ fn test_optimal() { + BONUS_NON_WORD, ), // this case is a cool example of why our algorithm is more than fzf - // we handle this corretly detect that it's better to match + // we handle this correctly detect that it's better to match // the second f instead of the third yielding a higher score // (despite using the same scoring function!) ( diff --git a/crates/atuin-nucleo/matcher/src/utf32_str.rs b/crates/atuin-nucleo/matcher/src/utf32_str.rs index 664dae7a..77bd9d51 100644 --- a/crates/atuin-nucleo/matcher/src/utf32_str.rs +++ b/crates/atuin-nucleo/matcher/src/utf32_str.rs @@ -45,14 +45,14 @@ fn has_ascii_graphemes(string: &str) -> bool { /// In the presence of a multi-codepoint grapheme (e.g. `"u\u{0308}"` which is `u + /// COMBINING_DIAERESIS`), the trailing codepoints are truncated. /// ``` -/// # use nucleo_matcher::Utf32String; +/// # use atuin_nucleo_matcher::Utf32String; /// assert_eq!(Utf32String::from("u\u{0308}").to_string(), "u"); /// ``` /// /// ### Indexing is done by grapheme /// Indexing into a string is done by grapheme rather than by codepoint. /// ``` -/// # use nucleo_matcher::Utf32String; +/// # use atuin_nucleo_matcher::Utf32String; /// assert!(Utf32String::from("au\u{0308}").len() == 2); /// ``` /// @@ -60,7 +60,7 @@ fn has_ascii_graphemes(string: &str) -> bool { /// Since the windows-style newline `\r\n` is ASCII only but considered to be a single grapheme, /// strings containing `\r\n` will still result in a `Unicode` variant. /// ``` -/// # use nucleo_matcher::Utf32String; +/// # use atuin_nucleo_matcher::Utf32String; /// let s = Utf32String::from("\r\n"); /// assert!(!s.slice(..).is_ascii()); /// assert!(s.len() == 1); @@ -73,7 +73,7 @@ fn has_ascii_graphemes(string: &str) -> bool { /// much hassle to deal with), we want to quickly iterate over codepoints (up to 5 /// times) during matching. /// -/// Doing codepoint segmentation on the fly not only blows trough the cache +/// Doing codepoint segmentation on the fly not only blows through the cache /// (lookup tables and I-cache) but also has nontrivial runtime compared to the /// matching itself. Furthermore there are many extra optimizations available /// for ASCII only text, but checking each match has too much overhead. diff --git a/crates/atuin-nucleo/src/boxcar.rs b/crates/atuin-nucleo/src/boxcar.rs index 9b48809d..726f4dff 100644 --- a/crates/atuin-nucleo/src/boxcar.rs +++ b/crates/atuin-nucleo/src/boxcar.rs @@ -51,7 +51,7 @@ pub(crate) struct Vec { impl Vec { /// Constructs a new, empty `Vec` with the specified capacity and matcher columns. pub fn with_capacity(capacity: u32, columns: u32) -> Vec { - assert_ne!(columns, 0, "there must be atleast one matcher column"); + assert_ne!(columns, 0, "there must be at least one matcher column"); let init = match capacity { 0 => 0, // initialize enough buckets for `capacity` elements diff --git a/crates/atuin-nucleo/src/lib.rs b/crates/atuin-nucleo/src/lib.rs index 5a500481..efc7628c 100644 --- a/crates/atuin-nucleo/src/lib.rs +++ b/crates/atuin-nucleo/src/lib.rs @@ -16,7 +16,7 @@ The [`Nucleo`] struct serves as the main API entrypoint for this crate. Nucleo is used in the helix-editor and therefore has a large user base with lots or real world testing. The core matcher implementation is considered complete -and is unlikely to see major changes. The `nucleo-matcher` crate is finished and +and is unlikely to see major changes. The `atuin-nucleo-matcher` crate is finished and ready for widespread use, breaking changes should be very rare (a 1.0 release should not be far away). @@ -45,7 +45,7 @@ use rayon::ThreadPool; use crate::pattern::MultiPattern; use crate::worker::Worker; -pub use nucleo_matcher::{chars, Config, Matcher, Utf32Str, Utf32String}; +pub use atuin_nucleo_matcher::{chars, Config, Matcher, Utf32Str, Utf32String}; mod boxcar; mod par_sort; @@ -67,7 +67,7 @@ pub struct Item<'a, T> { /// and sent across threads. pub struct Injector { items: Arc>, - notify: Arc<(dyn Fn() + Sync + Send)>, + notify: Arc, } impl Clone for Injector { @@ -93,10 +93,10 @@ impl Injector { /// /// You should favor this function over `push` if at least one of the following is true: /// - the number of items you're adding can be computed beforehand and is typically larger - /// than 1k + /// than 1k /// - you're able to batch incoming items /// - you're adding items from multiple threads concurrently (this function results in less - /// contention) + /// contention) pub fn extend(&self, values: I, fill_columns: impl Fn(&T, &mut [Utf32String])) where I: IntoIterator + ExactSizeIterator, @@ -298,7 +298,7 @@ pub struct Nucleo { pool: ThreadPool, state: State, items: Arc>, - notify: Arc<(dyn Fn() + Sync + Send)>, + notify: Arc, snapshot: Snapshot, /// The pattern matched by this matcher. To update the match pattern /// [`MultiPattern::reparse`](`pattern::MultiPattern::reparse`) should be used. @@ -316,7 +316,7 @@ pub struct Nucleo { impl Nucleo { /// Constructs a new `nucleo` worker threadpool with the provided `config`. /// - /// `notify` is called everytime new information is available and + /// `notify` is called every time new information is available and /// [`tick`](Nucleo::tick) should be called. Note that `notify` is not /// debounced, that should be handled by the downstream crate (for example /// debouncing to only redraw at most every 1/60 seconds). @@ -329,7 +329,7 @@ impl Nucleo { /// number of columns cannot be changed after construction. pub fn new( config: Config, - notify: Arc<(dyn Fn() + Sync + Send)>, + notify: Arc, num_threads: Option, columns: u32, ) -> Self { diff --git a/crates/atuin-nucleo/src/pattern.rs b/crates/atuin-nucleo/src/pattern.rs index 816b0a31..a9663274 100644 --- a/crates/atuin-nucleo/src/pattern.rs +++ b/crates/atuin-nucleo/src/pattern.rs @@ -1,5 +1,5 @@ -pub use nucleo_matcher::pattern::{Atom, AtomKind, CaseMatching, Normalization, Pattern}; -use nucleo_matcher::{Matcher, Utf32String}; +pub use atuin_nucleo_matcher::pattern::{Atom, AtomKind, CaseMatching, Normalization, Pattern}; +use atuin_nucleo_matcher::{Matcher, Utf32String}; #[cfg(test)] mod tests; @@ -56,7 +56,7 @@ impl MultiPattern { .0 .atoms .last() - .map_or(true, |last| !last.negative) + .is_none_or(|last| !last.negative) { self.cols[column].1 = Status::Update; } else { @@ -86,7 +86,7 @@ impl MultiPattern { } pub fn score(&self, haystack: &[Utf32String], matcher: &mut Matcher) -> Option { - // TODO: wheight columns? + // TODO: weight columns? let mut score = 0; for ((pattern, _), haystack) in self.cols.iter().zip(haystack) { score += pattern.score(haystack.slice(..), matcher)? diff --git a/crates/atuin-nucleo/src/pattern/tests.rs b/crates/atuin-nucleo/src/pattern/tests.rs index 40e8e328..59ed13f0 100644 --- a/crates/atuin-nucleo/src/pattern/tests.rs +++ b/crates/atuin-nucleo/src/pattern/tests.rs @@ -1,4 +1,4 @@ -use nucleo_matcher::pattern::{CaseMatching, Normalization}; +use atuin_nucleo_matcher::pattern::{CaseMatching, Normalization}; use crate::pattern::{MultiPattern, Status}; diff --git a/crates/atuin-nucleo/src/tests.rs b/crates/atuin-nucleo/src/tests.rs index 96c4d99c..1052264a 100644 --- a/crates/atuin-nucleo/src/tests.rs +++ b/crates/atuin-nucleo/src/tests.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use nucleo_matcher::Config; +use atuin_nucleo_matcher::Config; use crate::{pattern, Nucleo}; diff --git a/crates/atuin-nucleo/src/worker.rs b/crates/atuin-nucleo/src/worker.rs index ddd546ad..45e27cee 100644 --- a/crates/atuin-nucleo/src/worker.rs +++ b/crates/atuin-nucleo/src/worker.rs @@ -3,7 +3,7 @@ use std::mem::take; use std::sync::atomic::{self, AtomicBool, AtomicU32}; use std::sync::Arc; -use nucleo_matcher::Config; +use atuin_nucleo_matcher::Config; use parking_lot::Mutex; use rayon::{prelude::*, ThreadPool}; @@ -11,12 +11,12 @@ use crate::par_sort::par_quicksort; use crate::pattern::{self, MultiPattern}; use crate::{boxcar, Filter, Match, Scorer}; -struct Matchers(Box<[UnsafeCell]>); +struct Matchers(Box<[UnsafeCell]>); impl Matchers { // this is not a true mut from ref, we use a cell here #[allow(clippy::mut_from_ref)] - unsafe fn get(&self) -> &mut nucleo_matcher::Matcher { + unsafe fn get(&self) -> &mut atuin_nucleo_matcher::Matcher { &mut *self.0[rayon::current_thread_index().unwrap()].get() } } @@ -35,7 +35,7 @@ pub(crate) struct Worker { pub(crate) should_notify: Arc, pub(crate) was_canceled: bool, pub(crate) last_snapshot: u32, - notify: Arc<(dyn Fn() + Sync + Send)>, + notify: Arc, pub(crate) items: Arc>, in_flight: Vec, pub(crate) filter: Option>, @@ -69,7 +69,7 @@ impl Worker { pub(crate) fn new( worker_threads: Option, config: Config, - notify: Arc<(dyn Fn() + Sync + Send)>, + notify: Arc, cols: u32, ) -> (ThreadPool, Self) { let worker_threads = worker_threads @@ -80,7 +80,7 @@ impl Worker { .build() .expect("creating threadpool failed"); let matchers = (0..worker_threads) - .map(|_| UnsafeCell::new(nucleo_matcher::Matcher::new(config.clone()))) + .map(|_| UnsafeCell::new(atuin_nucleo_matcher::Matcher::new(config.clone()))) .collect(); let worker = Worker { running: false, diff --git a/crates/atuin/Cargo.toml b/crates/atuin/Cargo.toml index 5eed945d..6f06c648 100644 --- a/crates/atuin/Cargo.toml +++ b/crates/atuin/Cargo.toml @@ -87,7 +87,7 @@ uuid = { workspace = true } sysinfo = "0.30.7" regex = "1.10.5" norm = { version = "0.1.1", features = ["fzf-v2"] } -nucleo-matcher = { git = "https://github.com/atuinsh/nucleo-ext.git", rev = "74bd786" } +atuin-nucleo-matcher = { workspace = true } tempfile = { workspace = true } shlex = "1.3.0" diff --git a/crates/atuin/src/command/client/search/engines/daemon.rs b/crates/atuin/src/command/client/search/engines/daemon.rs index d317a4f6..9518fcb2 100644 --- a/crates/atuin/src/command/client/search/engines/daemon.rs +++ b/crates/atuin/src/command/client/search/engines/daemon.rs @@ -5,11 +5,11 @@ use atuin_client::{ settings::{SearchMode, Settings}, }; use atuin_daemon::client::SearchClient; -use eyre::Result; -use nucleo_matcher::{ +use atuin_nucleo_matcher::{ Config, Matcher, Utf32Str, pattern::{CaseMatching, Normalization, Pattern}, }; +use eyre::Result; use tracing::{Level, debug, instrument, span}; use uuid::Uuid; -- cgit v1.3.1