Skip to content

Commit

Permalink
Release version 0.4 (#41)
Browse files Browse the repository at this point in the history
* fix false positives for unicode substring matcher

* add note to docs about using the high level nucleo crate

* expose number of active injectors from matcher

* fix clippy lint

* release version 0.4
  • Loading branch information
pascalkuthe authored Feb 20, 2024
1 parent c7893db commit 9e234de
Show file tree
Hide file tree
Showing 11 changed files with 149 additions and 60 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
# Changelog

# [0.3.1] - 2023-12-22
# [0.4.0] - 2023-12-22

## Added

* `active_injectors()` to retrieve the number of injectors that can potentially add new items to the matcher in the future.

## Bugfixes

* fix Unicode substring matcher expecting an exact match (rejecting trailing characters)
* fix crashes and false positives in unicode substring matcher

# [0.3.0] - 2023-12-22

Expand Down
4 changes: 2 additions & 2 deletions Cargo.lock

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

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
[package]
name = "nucleo"
description = "plug and play high performance fuzzy matcher"
authors = ["Pascal Kuthe <pascal.kuthe@semimod.de>"]
version = "0.3.0"
authors = ["Pascal Kuthe <pascalkuthe@pm.me>"]
version = "0.4.0"
edition = "2021"
license = "MPL-2.0"
repository = "https://github.com/helix-editor/nucleo"
Expand All @@ -12,7 +12,7 @@ exclude = ["/typos.toml", "/tarpaulin.toml"]
[lib]

[dependencies]
nucleo-matcher = { version = "0.3.0", path = "matcher" }
nucleo-matcher = { version = "0.3.1", path = "matcher" }
parking_lot = { version = "0.12.1", features = ["send_guard", "arc_lock"]}
rayon = "1.7.0"

Expand Down
2 changes: 1 addition & 1 deletion bench/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
nucleo = { version = "0.3", path = "../" }
nucleo = { version = "*", path = "../" }
brunch = "0.5.0"
fuzzy-matcher = "0.3.7"
walkdir = "2"
4 changes: 2 additions & 2 deletions matcher/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
[package]
name = "nucleo-matcher"
description = "plug and play high performance fuzzy matcher"
authors = ["Pascal Kuthe <pascal.kuthe@semimod.de>"]
version = "0.3.0"
authors = ["Pascal Kuthe <pascalkuthe@pm.me>"]
version = "0.3.1"
edition = "2021"
license = "MPL-2.0"
repository = "https://github.com/helix-editor/nucleo"
Expand Down
7 changes: 4 additions & 3 deletions matcher/src/exact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,8 @@ impl Matcher {
.checked_sub(1)
.map(|i| haystack[i].char_class(&self.config))
.unwrap_or(self.config.initial_char_class);
for (i, &c) in haystack[start..].iter().enumerate() {
let end = haystack.len() - needle.len();
for (i, &c) in haystack[start..end].iter().enumerate() {
let (c, char_class) = c.char_class_and_normalize(&self.config);
if c != needle[0] {
continue;
Expand Down Expand Up @@ -265,8 +266,8 @@ impl Matcher {
let score = self.calculate_score::<INDICES, _, _>(
haystack,
needle,
max_pos,
max_pos + needle.len(),
start + max_pos,
start + max_pos + needle.len(),
indices,
);
Some(score)
Expand Down
5 changes: 5 additions & 0 deletions matcher/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
`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
meant to match a reasonably large number of items (> 100) using the high level
`nucleo` crate is highly recommended. Using `nucleo-matcher` directly in you ui
loop will be very slow. Implementing this logic yourself is very complex.
The matcher is hightly optimized and can significantly outperform `fzf` and
`skim` (the `fuzzy-matcher` crate). However some of these optimizations require
a slightly less convenient API. Be sure to carefully read the documentation of
Expand Down
3 changes: 3 additions & 0 deletions matcher/src/prefilter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ impl Matcher {
.position(|c| c.normalize(&self.config) == needle_char)?;
let needle_char = needle.last();
if only_greedy {
if haystack.len() - start < needle.len() {
return None;
}
Some((start, start + 1))
} else {
let end = haystack.len()
Expand Down
89 changes: 50 additions & 39 deletions matcher/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,20 +84,17 @@ fn assert_matches(
}
}

pub fn assert_not_matches(
fn assert_not_matches_with(
normalize: bool,
case_sensitive: bool,
path: bool,
algorithm: &[Algorithm],
cases: &[(&str, &str)],
) {
let mut config = Config {
let config = Config {
normalize,
ignore_case: !case_sensitive,
..Config::DEFAULT
};
if path {
config.set_match_paths();
}
let mut matcher = Matcher::new(config);
let mut needle_buf = Vec::new();
let mut haystack_buf = Vec::new();
Expand All @@ -110,31 +107,32 @@ pub fn assert_not_matches(
let needle = Utf32Str::new(&needle, &mut needle_buf);
let haystack = Utf32Str::new(haystack, &mut haystack_buf);

let res = matcher.fuzzy_match(haystack, needle);
assert_eq!(res, None, "{needle:?} should not match {haystack:?}");
let res = matcher.fuzzy_match_greedy(haystack, needle);
assert_eq!(
res, None,
"{needle:?} should not match {haystack:?} (greedy)"
);
let res = matcher.substring_match(haystack, needle);
assert_eq!(
res, None,
"{needle:?} should not match {haystack:?} (substring)"
);
let res = matcher.prefix_match(haystack, needle);
assert_eq!(
res, None,
"{needle:?} should not match {haystack:?} (prefix)"
);
let res = matcher.postfix_match(haystack, needle);
assert_eq!(
res, None,
"{needle:?} should not match {haystack:?} (postfix)"
);
for algo in algorithm {
let res = match algo {
FuzzyOptimal => matcher.fuzzy_match(haystack, needle),
FuzzyGreedy => matcher.fuzzy_match_greedy(haystack, needle),
Substring => matcher.substring_match(haystack, needle),
Prefix => matcher.prefix_match(haystack, needle),
Postfix => matcher.postfix_match(haystack, needle),
Exact => matcher.exact_match(haystack, needle),
};
assert_eq!(
res, None,
"{needle:?} should not match {haystack:?} {algo:?}"
);
}
}
}

pub fn assert_not_matches(normalize: bool, case_sensitive: bool, cases: &[(&str, &str)]) {
assert_not_matches_with(
normalize,
case_sensitive,
&[FuzzyOptimal, FuzzyGreedy, Substring, Prefix, Postfix, Exact],
cases,
)
}

const BONUS_BOUNDARY_WHITE: u16 = Config::DEFAULT.bonus_boundary_white;
const BONUS_BOUNDARY_DELIMITER: u16 = Config::DEFAULT.bonus_boundary_delimiter;

Expand Down Expand Up @@ -377,6 +375,15 @@ fn test_substring() {
),
],
);
assert_not_matches_with(
true,
false,
&[Prefix, Substring, Postfix, Exact],
&[(
"At the Road’s End - Seeming - SOL: A Self-Banishment Ritual",
"adi",
)],
)
}

#[test]
Expand Down Expand Up @@ -476,12 +483,20 @@ fn test_unicode() {
false,
false,
false,
&[(
"你好世界",
"你好",
&[0, 1],
BONUS_BOUNDARY_WHITE * (BONUS_FIRST_CHAR_MULTIPLIER + 1),
)],
&[
(
"你好世界",
"你好",
&[0, 1],
BONUS_BOUNDARY_WHITE * (BONUS_FIRST_CHAR_MULTIPLIER + 1),
),
(
" 你好世界",
"你好",
&[1, 2],
BONUS_BOUNDARY_WHITE * (BONUS_FIRST_CHAR_MULTIPLIER + 1),
),
],
);
assert_matches(
&[FuzzyGreedy, FuzzyOptimal],
Expand All @@ -497,7 +512,6 @@ fn test_unicode() {
)],
);
assert_not_matches(
false,
false,
false,
&[("Flibbertigibbet / イタズラっ子たち", "lying")],
Expand Down Expand Up @@ -614,7 +628,6 @@ fn test_reject() {
assert_not_matches(
true,
false,
false,
&[
("你好界", "abc"),
("你好界", "a"),
Expand All @@ -627,7 +640,6 @@ fn test_reject() {
assert_not_matches(
true,
true,
false,
&[
("你好界", "abc"),
("abc", "你"),
Expand All @@ -643,14 +655,13 @@ fn test_reject() {
assert_not_matches(
false,
true,
false,
&[
("Só Danço Samba", "sod"),
("Só Danço Samba", "soc"),
("Só Danç", "So"),
],
);
assert_not_matches(false, false, false, &[("ۂۂfoۂۂ", "foo")]);
assert_not_matches(false, false, &[("ۂۂfoۂۂ", "foo")]);
}

#[test]
Expand Down
Loading

0 comments on commit 9e234de

Please sign in to comment.