Skip to content

Commit ea2d4b0

Browse files
committed
Review changes
1 parent 111ba1a commit ea2d4b0

File tree

5 files changed

+59
-47
lines changed

5 files changed

+59
-47
lines changed

Cargo.lock

+1-1
Original file line numberDiff line numberDiff line change
@@ -6704,6 +6704,7 @@ dependencies = [
67046704
name = "re_ui"
67056705
version = "0.22.0-alpha.1+dev"
67066706
dependencies = [
6707+
"ahash",
67076708
"arrow",
67086709
"eframe",
67096710
"egui",
@@ -6713,7 +6714,6 @@ dependencies = [
67136714
"egui_tiles",
67146715
"getrandom",
67156716
"itertools 0.13.0",
6716-
"nohash-hasher",
67176717
"once_cell",
67186718
"parking_lot",
67196719
"rand",

crates/viewer/re_blueprint_tree/src/data.rs

+25-21
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,7 @@ impl DataResultData {
406406
NotLeaf(&'a DataResultNode),
407407
}
408408

409+
/// Temporary structure to hold local information.
409410
struct NodeInfo<'a> {
410411
leaf_or_not: LeafOrNot<'a>,
411412
kind: DataResultKind,
@@ -523,27 +524,30 @@ impl DataResultData {
523524
}
524525
};
525526

526-
let highlight_sections = hierarchy_highlights.remove(hierarchy.len().saturating_sub(1));
527-
528-
// never highlight the placeholder
529-
let highlight_sections =
530-
if node_info.kind == DataResultKind::OriginProjectionPlaceholder {
531-
SmallVec::new()
532-
} else {
533-
highlight_sections
534-
.map(Iterator::collect)
535-
.unwrap_or_default()
536-
};
537-
538-
is_this_a_match.then(|| Self {
539-
kind: node_info.kind,
540-
entity_path,
541-
visible,
542-
view_id: view_blueprint.id,
543-
label,
544-
highlight_sections,
545-
default_open: node_info.default_open,
546-
children,
527+
is_this_a_match.then(|| {
528+
let highlight_sections =
529+
hierarchy_highlights.remove(hierarchy.len().saturating_sub(1));
530+
531+
// never highlight the placeholder
532+
let highlight_sections =
533+
if node_info.kind == DataResultKind::OriginProjectionPlaceholder {
534+
SmallVec::new()
535+
} else {
536+
highlight_sections
537+
.map(Iterator::collect)
538+
.unwrap_or_default()
539+
};
540+
541+
Self {
542+
kind: node_info.kind,
543+
entity_path,
544+
visible,
545+
view_id: view_blueprint.id,
546+
label,
547+
highlight_sections,
548+
default_open: node_info.default_open,
549+
children,
550+
}
547551
})
548552
});
549553

crates/viewer/re_time_panel/src/streams_tree_data.rs

+1
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ impl EntityData {
128128
// Gather some info about the current node…
129129
//
130130

131+
/// Temporary structure to hold local information.
131132
struct NodeInfo {
132133
is_leaf: bool,
133134
is_this_a_match: bool,

crates/viewer/re_ui/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ re_log.workspace = true
4141
re_log_types.workspace = true # syntax-highlighting for EntityPath
4242
re_tracing.workspace = true
4343

44+
ahash.workspace = true
4445
arrow = { workspace = true, optional = true }
4546
eframe = { workspace = true, default-features = false, features = ["wgpu"] }
4647
egui_commonmark = { workspace = true, features = ["pulldown_cmark"] }
@@ -51,7 +52,6 @@ getrandom.workspace = true
5152
itertools.workspace = true
5253
once_cell.workspace = true
5354
parking_lot.workspace = true
54-
nohash-hasher.workspace = true
5555
serde = { workspace = true, features = ["derive"] }
5656
serde_json.workspace = true
5757
smallvec.workspace = true

crates/viewer/re_ui/src/filter_widget.rs

+31-24
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use std::ops::Range;
22

33
use egui::{Color32, NumExt as _, Widget as _};
44
use itertools::Itertools;
5-
use nohash_hasher::IntMap;
65
use smallvec::SmallVec;
76

87
use crate::{list_item, UiExt as _};
@@ -226,11 +225,11 @@ impl FilterMatcher {
226225
None => Some(PathRanges::default()),
227226
Some([]) => None,
228227
Some(keywords) => {
229-
let hierarchy = path.into_iter().map(str::to_lowercase).collect_vec();
228+
let path = path.into_iter().map(str::to_lowercase).collect_vec();
230229

231230
let all_ranges = keywords
232231
.iter()
233-
.map(|keyword| keyword.match_path(hierarchy.iter().map(String::as_str)))
232+
.map(|keyword| keyword.match_path(path.iter().map(String::as_str)))
234233
.collect_vec();
235234

236235
// all keywords must match!
@@ -256,13 +255,12 @@ impl FilterMatcher {
256255
/// `match_from_first_part_start` and/or `match_to_last_part_end`, which have the same behavior as
257256
/// regex's `^` and `$`.
258257
///
259-
/// If the keyword has multiple parts, the tested path must have at least one instance of contiguous
258+
/// If the keyword has multiple parts, e.g. "first/second", the tested path must have at least one instance of contiguous
260259
/// parts which match the corresponding keyword parts. In that context, the keyword parts have the
261260
/// following behavior:
262261
/// - First keyword part: `^part$` if `match_from_first_part_start`, `part$` otherwise
263262
/// - Last keyword part: `^part$` if `match_to_last_part_end`, `^part` otherwise
264263
/// - Other keyword parts: `^part$`
265-
266264
#[derive(Debug, Clone, PartialEq)]
267265
struct Keyword {
268266
/// The parts of a keyword.
@@ -279,6 +277,8 @@ impl Keyword {
279277
///
280278
/// The string must not contain any whitespace!
281279
fn new(mut keyword: &str) -> Self {
280+
// Invariant: keywords are not empty
281+
debug_assert!(!keyword.is_empty());
282282
debug_assert!(!keyword.contains(char::is_whitespace));
283283

284284
let match_from_first_part_start = if let Some(k) = keyword.strip_prefix('/') {
@@ -307,16 +307,25 @@ impl Keyword {
307307
/// Match the keyword against the provided path.
308308
///
309309
/// An empty [`PathRanges`] means that the keyword didn't match the path.
310-
fn match_path<'a>(&self, path: impl IntoIterator<Item = &'a str>) -> PathRanges {
310+
///
311+
/// Implementation notes:
312+
/// - This function is akin to a "sliding window" of the keyword parts against the path parts,
313+
/// trying to find some "alignment" yielding a match.
314+
/// - We must be thorough as we want to find _all_ match highlights (i.e., we don't early out as
315+
/// soon as we find a match).
316+
fn match_path<'a>(&self, lowercase_path: impl ExactSizeIterator<Item = &'a str>) -> PathRanges {
311317
let mut state_machines = vec![];
312318

313-
for (part_index, part) in path.into_iter().enumerate() {
314-
let lowercase_part = part.to_lowercase();
319+
let path_length = lowercase_path.len();
315320

316-
state_machines.push(MatchStateMachine::new(self));
321+
for (path_part_index, path_part) in lowercase_path.into_iter().enumerate() {
322+
// Only start a new state machine if it has a chance to be matched entirely.
323+
if self.parts.len() <= (path_length - path_part_index) {
324+
state_machines.push(MatchStateMachine::new(self));
325+
}
317326

318327
for state_machine in &mut state_machines {
319-
state_machine.step(&lowercase_part, part_index);
328+
state_machine.process_next_path_part(path_part, path_part_index);
320329
}
321330
}
322331

@@ -342,7 +351,7 @@ impl Keyword {
342351
/// merged when read, which only happens with [`Self::remove`].
343352
#[derive(Debug, Default)]
344353
pub struct PathRanges {
345-
ranges: IntMap<usize, Vec<Range<usize>>>,
354+
ranges: ahash::HashMap<usize, Vec<Range<usize>>>,
346355
}
347356

348357
impl PathRanges {
@@ -435,7 +444,7 @@ impl<'a> MatchStateMachine<'a> {
435444
matches!(self.state, MatchState::Match)
436445
}
437446

438-
fn step(&mut self, part: &str, part_index: usize) {
447+
fn process_next_path_part(&mut self, part: &str, part_index: usize) {
439448
if matches!(self.state, MatchState::Match | MatchState::NoMatch) {
440449
return;
441450
}
@@ -694,17 +703,17 @@ mod test {
694703

695704
#[test]
696705
fn test_keyword_match_path() {
697-
fn match_and_normalize(query: &str, path: &[&str]) -> Vec<Vec<Range<usize>>> {
698-
let keyword = Keyword::new(query);
699-
let path = path.to_vec();
700-
keyword.match_path(path.clone()).into_vec(path.len())
706+
fn match_and_normalize(query: &str, lowercase_path: &[&str]) -> Vec<Vec<Range<usize>>> {
707+
Keyword::new(query)
708+
.match_path(lowercase_path.iter().copied())
709+
.into_vec(lowercase_path.len())
701710
}
702711

703712
assert_eq!(match_and_normalize("a", &["a"]), vec![vec![0..1]]);
704713
assert_eq!(match_and_normalize("a", &["aaa"]), vec![vec![0..3]]);
705714

706715
assert_eq!(
707-
match_and_normalize("A/", &["aaa", "aaa"]),
716+
match_and_normalize("a/", &["aaa", "aaa"]),
708717
vec![vec![2..3], vec![2..3]]
709718
);
710719

@@ -737,31 +746,29 @@ mod test {
737746
);
738747

739748
assert!(
740-
match_and_normalize("a/B/c/", &["aaa", "b", "cccc"])
749+
match_and_normalize("a/b/c/", &["aaa", "b", "cccc"])
741750
.into_iter()
742751
.flatten()
743752
.count()
744753
== 0,
745754
);
746755

747756
assert_eq!(
748-
match_and_normalize("ab/cd", &["xxxAb", "cDaB", "Cdxxx"]),
757+
match_and_normalize("ab/cd", &["xxxab", "cdab", "cdxxx"]),
749758
vec![vec![3..5], vec![0..4], vec![0..2]]
750759
);
751760

752761
assert_eq!(
753-
match_and_normalize("ab/ab", &["xxxAb", "aB", "aBxxx"]),
762+
match_and_normalize("ab/ab", &["xxxab", "ab", "abxxx"]),
754763
vec![vec![3..5], vec![0..2], vec![0..2]]
755764
);
756765
}
757766

758767
#[test]
759768
fn test_match_path() {
760769
fn match_and_normalize(query: &str, path: &[&str]) -> Option<Vec<Vec<Range<usize>>>> {
761-
let matcher = FilterMatcher::new(Some(query));
762-
let path = path.to_vec();
763-
matcher
764-
.match_path(path.clone())
770+
FilterMatcher::new(Some(query))
771+
.match_path(path.iter().copied())
765772
.map(|ranges| ranges.into_vec(path.len()))
766773
}
767774

0 commit comments

Comments
 (0)