1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
use nucleo_matcher::pattern::{CaseMatching, Normalization, Pattern};
use nucleo_matcher::{Matcher, Utf32Str};
use rayon::prelude::*;
use std::hash::Hash;
use crate::{ColumnOperations, ColumnOrdering, SelectableRow, SelectableTable, SortOrder};
impl<Row, F, Conf> SelectableTable<Row, F, Conf>
where
Row: Clone + Send + Sync,
F: Eq
+ Hash
+ Clone
+ Ord
+ Send
+ Sync
+ Default
+ ColumnOperations<Row, F, Conf>
+ ColumnOrdering<Row>,
Conf: Default,
{
/// Performs a fuzzy search using specified columns across all rows and updates the displayed rows.
///
/// This function filters the table rows based on a search `query` using `nucleo-matcher`
/// crate.
/// It checks the specified `column_list` for each row, concatenates their string representations,
/// and scores them using the provided or generated `Pattern`. Only rows with a non-`None` score
/// are retained.
///
/// If a `limit` is provided, it will result at most `limit` rows.
///
/// # Parameters:
/// - `column_list`: A list of columns to search across. Does nothing if empty.
/// - `query`: The search string. Does nothing if empty.
/// - `limit`: Optional limit on the number of results returned. Does nothing if `0`. Defaults
/// to no limit
/// - `pattern`: Optional precomputed fuzzy `Pattern`. Default pattern is created from the query using
/// case-insensitive matching and smart normalization.
///
/// The search is relatively fast even with a million rows but it should not be called every
/// frame and be used sparingly.
///
/// To reset search results, call [`recreate_rows`](SelectableTable::recreate_rows).
///
/// # Example:
/// ```rust,ignore
/// table.search_and_show(&vec![Column::Name, Column::Username], "john", Some(10), None);
/// ```
pub fn search_and_show(
&mut self,
column_list: &Vec<F>,
query: &str,
limit: Option<usize>,
pattern: Option<Pattern>,
) {
if query.is_empty() {
return;
}
if column_list.is_empty() {
return;
}
if let Some(limit) = limit
&& limit == 0
{
return;
}
let pattern = pattern
.unwrap_or_else(|| Pattern::parse(query, CaseMatching::Ignore, Normalization::Smart));
let mut buf = Vec::new();
let mut row_data: Vec<SelectableRow<Row, F>> = Vec::new();
for val in self.rows.values() {
let mut string_val = String::new();
for column in column_list {
let value = column.column_text(&val.row_data);
string_val.push_str(&value);
string_val.push(' ');
}
if pattern
.score(Utf32Str::new(&string_val, &mut buf), &mut self.matcher)
.is_some()
{
row_data.push(val.clone());
if let Some(max) = limit
&& row_data.len() >= max
{
break;
}
}
}
self.formatted_rows.clear();
self.active_rows.clear();
self.active_columns.clear();
row_data.par_sort_by(|a, b| {
let ordering = self.sorted_by.order_by(&a.row_data, &b.row_data);
match self.sort_order {
SortOrder::Ascending => ordering,
SortOrder::Descending => ordering.reverse(),
}
});
self.indexed_ids = row_data
.par_iter()
.enumerate()
.map(|(index, row)| (row.id, index))
.collect();
self.formatted_rows = row_data;
}
/// Sets a custom matcher to use for fuzzy searching rows
///
/// This allows the table to use a custom `Matcher` from `nucleo-matcher` crate
/// for searching/filtering through rows based on the input text. Use this to change
/// the search algorithm or tweak scoring behavior.
///
/// # Parameters:
/// - `matcher`: The matcher instance to use for row filtering.
///
/// # Returns:
/// - `Self`: The modified table with the specified matcher applied.
///
/// # Example:
/// ```rust,ignore
/// let matcher = Matcher::default();
/// let table = SelectableTable::new(columns)
/// .matcher(matcher);
/// ```
#[must_use]
pub fn matcher(mut self, matcher: Matcher) -> Self {
self.matcher = matcher;
self
}
/// Replaces the current matcher with a new one.
///
/// This method allows updating the fuzzy search matcher dynamically.
///
/// # Parameters:
/// - `matcher`: The new matcher instance to set.
///
/// # Example:
/// ```rust,ignore
/// table.set_matcher(new_matcher);
/// ```
pub fn set_matcher(&mut self, matcher: Matcher) {
self.matcher = matcher;
}
}