Skip to content

Commit 5b0323a

Browse files
committed
feat!: use Arinae as default algorithm
=== Results: ./target/release/sk [baseline] === Completed runs: 50 / 50 Average items matched: 2895782 / 10000000 (min: 2895782, max: 2895782) Average time: 3.827s (min: 3.576s, max: 4.090s) Average items/second: 2615767 (min: 2445033, max: 2796365) Average peak memory usage: 1589.2 MB (min: 1518.6 MB, max: 1661.2 MB) Average peak CPU usage: 528.9% (min: 457.0%, max: 740.0%) === Results: /home/loric/.local/share/cargo/bin/sk === Completed runs: 50 / 50 Average items matched: 2895782 / 10000000 (min: 2895782, max: 2895782) +0.0% Average time: 3.930s (min: 3.565s, max: 4.226s) +2.7% Average items/second: 2548674 (min: 2366263, max: 2804816) -2.6% Average peak memory usage: 1618.8 MB (min: 1539.1 MB, max: 1680.6 MB) +1.9% Average peak CPU usage: 696.8% (min: 608.0%, max: 875.0%) +31.7% === Results: /home/loric/.nix-profile/bin/fzf === Completed runs: 50 / 50 Average items matched: 2895782 / 10000000 (min: 2895782, max: 2895782) +0.0% Average time: 5.421s (min: 4.814s, max: 6.111s) +41.7% Average items/second: 1848269 (min: 1636444, max: 2077385) -29.3% Average peak memory usage: 2015.3 MB (min: 1860.7 MB, max: 2173.9 MB) +26.8% Average peak CPU usage: 1301.1% (min: 1229.0%, max: 1431.0%) +146.0% === Comparison Summary (vs baseline: ./target/release/sk) === Binary Avg time Δ time Avg rate Δ rate ------------------------------------------------------------------------------------------ ./target/release/sk 3.827s baseline 2615767 baseline /home/loric/.local/share/cargo/bin/sk 3.930s +2.7% 2548674 -2.6% /home/loric/.nix-profile/bin/fzf 5.421s +41.7% 1848269 -29.3%
1 parent 86d824e commit 5b0323a

File tree

9 files changed

+19
-269
lines changed

9 files changed

+19
-269
lines changed

src/engine/fuzzy.rs

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,17 @@ use crate::{MatchRange, MatchResult, SkimItem};
1717
#[cfg_attr(feature = "cli", derive(clap::ValueEnum))]
1818
#[cfg_attr(feature = "cli", clap(rename_all = "snake_case"))]
1919
pub enum FuzzyAlgorithm {
20-
/// Original skim fuzzy matching algorithm (v1)
21-
SkimV1,
22-
/// Improved skim fuzzy matching algorithm (v2, default)
23-
#[default]
20+
/// Improved skim fuzzy matching algorithm (v2)
2421
SkimV2,
2522
/// Clangd fuzzy matching algorithm
2623
Clangd,
2724
/// Fzy matching algorithm (https://github.com/jhawthorn/fzy)
2825
Fzy,
2926
/// Frizbee matching algorithm, typo resistant
3027
Frizbee,
31-
/// Arinae: typo-resistant & natural algorithm
28+
/// Arinae: typo-resistant & natural algorithm, default
3229
#[cfg_attr(feature = "cli", clap(alias = "ari"))]
30+
#[default]
3331
Arinae,
3432
}
3533

@@ -107,15 +105,10 @@ impl FuzzyEngineBuilder {
107105

108106
#[allow(deprecated)]
109107
pub fn build(self) -> FuzzyEngine {
110-
use crate::fuzzy_matcher::skim::SkimMatcher;
111108
#[allow(unused_mut)]
112109
let mut algorithm = self.algorithm;
113110
let max_typos = self.effective_max_typos();
114111
let matcher: Box<dyn FuzzyMatcher> = match algorithm {
115-
FuzzyAlgorithm::SkimV1 => {
116-
debug!("Initialized SkimV1 algorithm");
117-
Box::new(SkimMatcher::default())
118-
}
119112
FuzzyAlgorithm::SkimV2 => {
120113
let matcher = SkimMatcherV2::default().element_limit(BYTES_1M);
121114
let matcher = match self.case {

src/fuzzy_matcher/arinae/atom.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,6 @@ impl Atom for char {
8585
}
8686
#[inline(always)]
8787
fn is_lowercase(self) -> bool {
88-
self.is_ascii_lowercase()
88+
self.is_lowercase()
8989
}
9090
}

src/fuzzy_matcher/skim.rs

Lines changed: 0 additions & 240 deletions
Original file line numberDiff line numberDiff line change
@@ -26,243 +26,6 @@ use super::skim::Movement::{Match, Skip};
2626
use super::util::{char_equal, cheap_matches};
2727
use super::{FuzzyMatcher, IndexType, MatchIndices, ScoreType};
2828

29-
const BONUS_MATCHED: ScoreType = 4;
30-
const BONUS_CASE_MATCH: ScoreType = 4;
31-
const BONUS_UPPER_MATCH: ScoreType = 6;
32-
const BONUS_ADJACENCY: ScoreType = 10;
33-
const BONUS_SEPARATOR: ScoreType = 8;
34-
const BONUS_CAMEL: ScoreType = 8;
35-
const PENALTY_CASE_UNMATCHED: ScoreType = -1;
36-
const PENALTY_LEADING: ScoreType = -6;
37-
// penalty applied for every letter before the first match
38-
const PENALTY_MAX_LEADING: ScoreType = -18;
39-
// maxing penalty for leading letters
40-
const PENALTY_UNMATCHED: ScoreType = -2;
41-
42-
#[deprecated(since = "0.3.5", note = "Please use SkimMatcherV2 instead")]
43-
/// Legacy fuzzy matcher (V1) - deprecated, use SkimMatcherV2 instead
44-
#[derive(Default, Debug)]
45-
pub struct SkimMatcher {}
46-
47-
/// The V1 matcher is based on ForrestTheWoods's post
48-
/// https://www.forrestthewoods.com/blog/reverse_engineering_sublime_texts_fuzzy_match/
49-
///
50-
/// V1 algorithm is deprecated, checkout `FuzzyMatcherV2`
51-
impl FuzzyMatcher for SkimMatcher {
52-
fn fuzzy_indices(&self, choice: &str, pattern: &str) -> Option<(ScoreType, MatchIndices)> {
53-
fuzzy_indices(choice, pattern).map(|(s, v)| (s, MatchIndices::from(v)))
54-
}
55-
56-
fn fuzzy_match(&self, choice: &str, pattern: &str) -> Option<ScoreType> {
57-
fuzzy_match(choice, pattern)
58-
}
59-
}
60-
61-
#[deprecated(since = "0.3.5", note = "Please use SkimMatcherV2 instead")]
62-
/// Legacy fuzzy matching function - returns match score only
63-
pub fn fuzzy_match(choice: &str, pattern: &str) -> Option<ScoreType> {
64-
if pattern.is_empty() {
65-
return Some(0);
66-
}
67-
68-
let scores = build_graph(choice, pattern)?;
69-
70-
let last_row = &scores[scores.len() - 1];
71-
let (_, &MatchingStatus { final_score, .. }) = last_row
72-
.iter()
73-
.enumerate()
74-
.max_by_key(|&(_, x)| x.final_score)
75-
.expect("fuzzy_indices failed to iterate over last_row");
76-
Some(final_score)
77-
}
78-
79-
#[deprecated(since = "0.3.5", note = "Please use SkimMatcherV2 instead")]
80-
/// Legacy fuzzy matching function - returns match score and character indices
81-
pub fn fuzzy_indices(choice: &str, pattern: &str) -> Option<(ScoreType, Vec<IndexType>)> {
82-
if pattern.is_empty() {
83-
return Some((0, Vec::new()));
84-
}
85-
86-
let mut picked = vec![];
87-
let scores = build_graph(choice, pattern)?;
88-
89-
let last_row = &scores[scores.len() - 1];
90-
let (mut next_col, &MatchingStatus { final_score, .. }) = last_row
91-
.iter()
92-
.enumerate()
93-
.max_by_key(|&(_, x)| x.final_score)
94-
.expect("fuzzy_indices failed to iterate over last_row");
95-
let mut pat_idx = scores.len() as i64 - 1;
96-
while pat_idx >= 0 {
97-
let status = scores[pat_idx as usize][next_col];
98-
next_col = status.back_ref as usize;
99-
picked.push(status.idx);
100-
pat_idx -= 1;
101-
}
102-
picked.reverse();
103-
Some((final_score, picked))
104-
}
105-
106-
#[derive(Clone, Copy, Debug)]
107-
struct MatchingStatus {
108-
pub idx: IndexType,
109-
pub score: ScoreType,
110-
pub final_score: ScoreType,
111-
pub adj_num: IndexType,
112-
pub back_ref: IndexType,
113-
}
114-
115-
impl Default for MatchingStatus {
116-
fn default() -> Self {
117-
MatchingStatus {
118-
idx: 0,
119-
score: 0,
120-
final_score: 0,
121-
adj_num: 1,
122-
back_ref: 0,
123-
}
124-
}
125-
}
126-
127-
fn build_graph(choice: &str, pattern: &str) -> Option<Vec<Vec<MatchingStatus>>> {
128-
let mut scores = vec![];
129-
130-
let mut match_start_idx = 0; // to ensure that the pushed char are able to match the pattern
131-
let mut pat_prev_ch = '\0';
132-
133-
// initialize the match positions and inline scores
134-
for (pat_idx, pat_ch) in pattern.chars().enumerate() {
135-
let mut vec = vec![];
136-
let mut choice_prev_ch = '\0';
137-
for (idx, ch) in choice.chars().enumerate() {
138-
if char_equal(ch, pat_ch, false) && idx >= match_start_idx {
139-
let score = fuzzy_score(
140-
ch,
141-
idx as IndexType,
142-
choice_prev_ch,
143-
pat_ch,
144-
pat_idx as IndexType,
145-
pat_prev_ch,
146-
);
147-
vec.push(MatchingStatus {
148-
idx: idx as IndexType,
149-
score,
150-
final_score: score,
151-
adj_num: 1,
152-
back_ref: 0,
153-
});
154-
}
155-
choice_prev_ch = ch;
156-
}
157-
158-
if vec.is_empty() {
159-
// not matched
160-
return None;
161-
}
162-
match_start_idx = vec[0].idx + 1;
163-
scores.push(vec);
164-
pat_prev_ch = pat_ch;
165-
}
166-
167-
// calculate max scores considering adjacent characters
168-
for pat_idx in 1..scores.len() {
169-
let (first_half, last_half) = scores.split_at_mut(pat_idx);
170-
171-
let prev_row = &first_half[first_half.len() - 1];
172-
let cur_row = &mut last_half[0];
173-
174-
for idx in 0..cur_row.len() {
175-
let next = cur_row[idx];
176-
let prev = if idx > 0 {
177-
cur_row[idx - 1]
178-
} else {
179-
MatchingStatus::default()
180-
};
181-
182-
let mut score_before_idx = prev.final_score - prev.score + next.score;
183-
score_before_idx += PENALTY_UNMATCHED * ((next.idx - prev.idx) as ScoreType);
184-
score_before_idx -= if prev.adj_num == 0 { BONUS_ADJACENCY } else { 0 };
185-
186-
let (back_ref, score, adj_num) = prev_row
187-
.iter()
188-
.enumerate()
189-
.take_while(|&(_, &MatchingStatus { idx, .. })| idx < next.idx)
190-
.skip_while(|&(_, &MatchingStatus { idx, .. })| idx < prev.idx)
191-
.map(|(back_ref, cur)| {
192-
let adj_num = next.idx - cur.idx - 1;
193-
let mut final_score = cur.final_score + next.score;
194-
final_score += if adj_num == 0 {
195-
BONUS_ADJACENCY
196-
} else {
197-
PENALTY_UNMATCHED * adj_num as ScoreType
198-
};
199-
(back_ref, final_score, adj_num)
200-
})
201-
.max_by_key(|&(_, x, _)| x)
202-
.unwrap_or((prev.back_ref, score_before_idx, prev.adj_num));
203-
204-
cur_row[idx] = if idx > 0 && score < score_before_idx {
205-
MatchingStatus {
206-
final_score: score_before_idx,
207-
back_ref: prev.back_ref,
208-
adj_num,
209-
..next
210-
}
211-
} else {
212-
MatchingStatus {
213-
final_score: score,
214-
back_ref: back_ref as IndexType,
215-
adj_num,
216-
..next
217-
}
218-
};
219-
}
220-
}
221-
222-
Some(scores)
223-
}
224-
225-
// judge how many scores the current index should get
226-
fn fuzzy_score(
227-
choice_ch: char,
228-
choice_idx: IndexType,
229-
choice_prev_ch: char,
230-
pat_ch: char,
231-
pat_idx: IndexType,
232-
_pat_prev_ch: char,
233-
) -> ScoreType {
234-
let mut score = BONUS_MATCHED;
235-
236-
let choice_prev_ch_type = CharType::of(choice_prev_ch);
237-
let choice_role = CharRole::of(choice_prev_ch, choice_ch);
238-
239-
if pat_ch == choice_ch {
240-
if pat_ch.is_uppercase() {
241-
score += BONUS_UPPER_MATCH;
242-
} else {
243-
score += BONUS_CASE_MATCH;
244-
}
245-
} else {
246-
score += PENALTY_CASE_UNMATCHED;
247-
}
248-
249-
// apply bonus for camelCases
250-
if choice_role == CharRole::Head || choice_role == CharRole::Break || choice_role == CharRole::Camel {
251-
score += BONUS_CAMEL;
252-
}
253-
254-
// apply bonus for matches after a separator
255-
if choice_prev_ch_type == CharType::HardSep || choice_prev_ch_type == CharType::SoftSep {
256-
score += BONUS_SEPARATOR;
257-
}
258-
259-
if pat_idx == 0 {
260-
score += max((choice_idx as ScoreType) * PENALTY_LEADING, PENALTY_MAX_LEADING);
261-
}
262-
263-
score
264-
}
265-
26629
#[derive(Copy, Clone, Debug)]
26730
/// Configuration for skim's scoring algorithm
26831
pub struct SkimScoreConfig {
@@ -520,9 +283,6 @@ enum CharRole {
520283
}
521284

522285
impl CharRole {
523-
pub fn of(prev: char, cur: char) -> Self {
524-
Self::of_type(CharType::of(prev), CharType::of(cur))
525-
}
526286
pub fn of_type(prev: CharType, cur: CharType) -> Self {
527287
match (prev, cur) {
528288
(CharType::Empty, _) | (CharType::HardSep, _) => CharRole::Head,

src/options.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -175,16 +175,16 @@ pub struct SkimOptions {
175175

176176
/// Fuzzy matching algorithm
177177
///
178-
/// skim_v2 Latest skim algorithm, should be better in almost any case
179-
/// skim_v1 Legacy skim algorithm
178+
/// arinae (ari) Latest algorithm
179+
/// skim_v2 Legacy skim algorithm
180180
/// clangd Used in clangd for keyword completion
181181
/// fzy Algorithm from fzy (https://github.com/jhawthorn/fzy)
182182
#[cfg_attr(
183183
feature = "cli",
184184
arg(
185185
long = "algo",
186-
default_value = "skim_v2",
187186
value_enum,
187+
default_value = "arinae",
188188
help_heading = "Search",
189189
verbatim_doc_comment
190190
)

tests/matcher.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,27 +61,24 @@ insta_test!(matcher_default, INPUT_ITEMS, &["-q", "stum"], {
6161
insta_test!(matcher_skim_v2, INPUT_ITEMS, &["-q", "stum", "--algo", "skim_v2"], {
6262
@snap;
6363
});
64-
insta_test!(matcher_skim_v1, INPUT_ITEMS, &["-q", "stum", "--algo", "skim_v1"], {
65-
@snap;
66-
});
6764
insta_test!(matcher_clangd, INPUT_ITEMS, &["-q", "stum", "--algo", "clangd"], {
6865
@snap;
6966
});
7067
insta_test!(matcher_frizbee, INPUT_ITEMS, &["-q", "stum", "--algo", "frizbee", "--no-typos"], {
7168
@snap;
7269
});
73-
insta_test!(matcher_frizbee_typos, INPUT_ITEMS, &["-q", "stum", "--algo", "frizbee"], {
70+
insta_test!(matcher_frizbee_typos, INPUT_ITEMS, &["-q", "stum", "--algo", "frizbee", "--typos"], {
7471
@snap;
7572
});
7673
insta_test!(matcher_fzy, INPUT_ITEMS, &["-q", "stum", "--algo", "fzy", "--no-typos"], {
7774
@snap;
7875
});
79-
insta_test!(matcher_fzy_typos, INPUT_ITEMS, &["-q", "stum", "--algo", "fzy"], {
76+
insta_test!(matcher_fzy_typos, INPUT_ITEMS, &["-q", "stum", "--algo", "fzy", "--typos"], {
8077
@snap;
8178
});
8279
insta_test!(matcher_arinae, INPUT_ITEMS, &["-q", "stum", "--algo", "arinae", "--no-typos"], {
8380
@snap;
8481
});
85-
insta_test!(matcher_arinae_typos, INPUT_ITEMS, &["-q", "stum", "--algo", "arinae"], {
82+
insta_test!(matcher_arinae_typos, INPUT_ITEMS, &["-q", "stum", "--algo", "arinae", "--typos"], {
8683
@snap;
8784
});

tests/snapshots/normalize__insta_normalize_case_insensitive-2.snap

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ expression: buf + & cursor_pos
2020
" "
2121
" "
2222
" "
23-
" CAFE "
24-
" Café "
2523
" cafe "
26-
"> café "
24+
" café "
25+
" CAFE "
26+
"> Café "
2727
" 4/4 0/0"
2828
"> cafe "
2929
cursor: (24, 7)

tests/snapshots/tiebreak__tiebreak_neg_pathname-2.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ expression: buf + & cursor_pos
2121
" "
2222
" "
2323
" "
24-
" baz/foo "
2524
" foo "
25+
" baz/foo "
2626
"> foo/bar "
2727
" 3/3 0/0"
2828
"> foo "

tests/snapshots/tiebreak__tiebreak_pathname-2.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ expression: buf + & cursor_pos
2222
" "
2323
" "
2424
" foo/bar "
25-
" baz/foo "
26-
"> foo "
25+
" foo "
26+
"> baz/foo "
2727
" 3/3 0/0"
2828
"> foo "
2929
cursor: (24, 6)

0 commit comments

Comments
 (0)