Coverage Report

Created: 2025-11-28 06:44

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/fuzzy-matcher-0.3.7/src/util.rs
Line
Count
Source
1
use crate::{FuzzyMatcher, IndexType, ScoreType};
2
3
0
pub fn cheap_matches(
4
0
    choice: &[char],
5
0
    pattern: &[char],
6
0
    case_sensitive: bool,
7
0
) -> Option<Vec<usize>> {
8
0
    let mut first_match_indices = vec![];
9
0
    let mut pattern_iter = pattern.iter().peekable();
10
0
    for (idx, &c) in choice.iter().enumerate() {
11
0
        match pattern_iter.peek() {
12
0
            Some(&&p) => {
13
0
                if char_equal(c, p, case_sensitive) {
14
0
                    first_match_indices.push(idx);
15
0
                    let _ = pattern_iter.next();
16
0
                }
17
            }
18
0
            None => break,
19
        }
20
    }
21
22
0
    if pattern_iter.peek().is_none() {
23
0
        Some(first_match_indices)
24
    } else {
25
0
        None
26
    }
27
0
}
28
29
/// Given 2 character, check if they are equal (considering ascii case)
30
/// e.g. ('a', 'A', true) => false
31
/// e.g. ('a', 'A', false) => true
32
#[inline]
33
0
pub fn char_equal(a: char, b: char, case_sensitive: bool) -> bool {
34
0
    if case_sensitive {
35
0
        a == b
36
    } else {
37
0
        a.eq_ignore_ascii_case(&b)
38
    }
39
0
}
40
41
#[derive(Debug, PartialEq)]
42
pub enum CharType {
43
    NonWord,
44
    Lower,
45
    Upper,
46
    Number,
47
}
48
49
#[inline]
50
0
pub fn char_type_of(ch: char) -> CharType {
51
0
    if ch.is_lowercase() {
52
0
        CharType::Lower
53
0
    } else if ch.is_uppercase() {
54
0
        CharType::Upper
55
0
    } else if ch.is_numeric() {
56
0
        CharType::Number
57
    } else {
58
0
        CharType::NonWord
59
    }
60
0
}
61
62
#[derive(Debug, PartialEq)]
63
pub enum CharRole {
64
    Tail,
65
    Head,
66
}
67
68
// checkout https://github.com/llvm-mirror/clang-tools-extra/blob/master/clangd/FuzzyMatch.cpp
69
// The Role can be determined from the Type of a character and its neighbors:
70
//
71
//   Example  | Chars | Type | Role
72
//   ---------+--------------+-----
73
//   F(o)oBar | Foo   | Ull  | Tail
74
//   Foo(B)ar | oBa   | lUl  | Head
75
//   (f)oo    | ^fo   | Ell  | Head
76
//   H(T)TP   | HTT   | UUU  | Tail
77
//
78
//      Curr= Empty Lower Upper Separ
79
// Prev=Empty 0x00, 0xaa, 0xaa, 0xff, // At start, Lower|Upper->Head
80
// Prev=Lower 0x00, 0x55, 0xaa, 0xff, // In word, Upper->Head;Lower->Tail
81
// Prev=Upper 0x00, 0x55, 0x59, 0xff, // Ditto, but U(U)U->Tail
82
// Prev=Separ 0x00, 0xaa, 0xaa, 0xff, // After separator, like at start
83
0
pub fn char_role(prev: char, cur: char) -> CharRole {
84
    use self::CharRole::*;
85
    use self::CharType::*;
86
0
    match (char_type_of(prev), char_type_of(cur)) {
87
0
        (Lower, Upper) | (NonWord, Lower) | (NonWord, Upper) => Head,
88
0
        _ => Tail,
89
    }
90
0
}
91
92
#[allow(dead_code)]
93
0
pub fn assert_order(matcher: &dyn FuzzyMatcher, pattern: &str, choices: &[&'static str]) {
94
0
    let result = filter_and_sort(matcher, pattern, choices);
95
96
0
    if result != choices {
97
        // debug print
98
0
        println!("pattern: {}", pattern);
99
0
        for &choice in choices.iter() {
100
0
            if let Some((score, indices)) = matcher.fuzzy_indices(choice, pattern) {
101
0
                println!("{}: {:?}", score, wrap_matches(choice, &indices));
102
0
            } else {
103
0
                println!("NO MATCH for {}", choice);
104
0
            }
105
        }
106
0
    }
107
108
0
    assert_eq!(result, choices);
109
0
}
110
111
#[allow(dead_code)]
112
0
pub fn filter_and_sort(
113
0
    matcher: &dyn FuzzyMatcher,
114
0
    pattern: &str,
115
0
    lines: &[&'static str],
116
0
) -> Vec<&'static str> {
117
0
    let mut lines_with_score: Vec<(ScoreType, &'static str)> = lines
118
0
        .iter()
119
0
        .filter_map(|&s| matcher.fuzzy_match(s, pattern).map(|score| (score, s)))
120
0
        .collect();
121
0
    lines_with_score.sort_by_key(|(score, _)| -score);
122
0
    lines_with_score
123
0
        .into_iter()
124
0
        .map(|(_, string)| string)
125
0
        .collect()
126
0
}
127
128
#[allow(dead_code)]
129
0
pub fn wrap_matches(line: &str, indices: &[IndexType]) -> String {
130
0
    let mut ret = String::new();
131
0
    let mut peekable = indices.iter().peekable();
132
0
    for (idx, ch) in line.chars().enumerate() {
133
0
        let next_id = **peekable.peek().unwrap_or(&&(line.len() as IndexType));
134
0
        if next_id == (idx as IndexType) {
135
0
            ret.push_str(format!("[{}]", ch).as_str());
136
0
            peekable.next();
137
0
        } else {
138
0
            ret.push(ch);
139
0
        }
140
    }
141
142
0
    ret
143
0
}