1use derive_more::Display;
3use egui::collapsing_header::CollapsingState;
4use egui::{Button, Layout, RichText, TextEdit, Ui};
5use egui_remixicon::icons;
6use emath::{Align, Vec2};
7use enum_iterator::Sequence;
8use fuzzy_matcher::{FuzzyMatcher, skim::SkimMatcherV2};
9use itertools::Itertools;
10use regex::{Regex, RegexBuilder, escape};
11use serde::{Deserialize, Serialize};
12use std::cell::RefCell;
13
14use crate::data_container::DataContainer::Transactions;
15use crate::transaction_container::{StreamScopeRef, TransactionStreamRef};
16use crate::variable_direction::VariableDirectionExt;
17use crate::wave_container::{VariableRefExt, WaveContainer};
18use crate::wave_data::ScopeType;
19use crate::{SystemState, message::Message, wave_container::VariableRef};
20use surfer_translation_types::VariableDirection;
21
22use std::cmp::Ordering;
23
24pub const VARIABLE_FILTER_ID: &str = "variable-filter";
25
26#[derive(Clone, Debug, Display, PartialEq, Serialize, Deserialize, Sequence)]
27pub enum VariableNameFilterType {
28 #[display("Fuzzy")]
29 Fuzzy,
30
31 #[display("Regular expression")]
32 Regex,
33
34 #[display("Variable starts with")]
35 Start,
36
37 #[display("Variable contains")]
38 Contain,
39}
40
41#[derive(Serialize, Deserialize)]
42pub struct VariableFilter {
43 pub(crate) name_filter_type: VariableNameFilterType,
44 pub(crate) name_filter_str: String,
45 pub(crate) name_filter_case_insensitive: bool,
46
47 pub(crate) include_inputs: bool,
48 pub(crate) include_outputs: bool,
49 pub(crate) include_inouts: bool,
50 pub(crate) include_others: bool,
51
52 pub(crate) group_by_direction: bool,
53 #[serde(skip)]
54 cache: RefCell<VariableFilterRegexCache>,
55}
56
57#[derive(Default)]
59struct VariableFilterRegexCache {
60 regex_pattern: Option<String>,
62 regex_case_insensitive: bool,
63 regex: Option<Regex>,
64 regex_error: Option<String>,
65}
66
67#[derive(Debug, Deserialize)]
68pub enum VariableIOFilterType {
69 Input,
70 Output,
71 InOut,
72 Other,
73}
74
75impl Default for VariableFilter {
76 fn default() -> Self {
77 Self::new()
78 }
79}
80
81impl VariableFilter {
82 #[must_use]
83 pub fn new() -> VariableFilter {
84 VariableFilter {
85 name_filter_type: VariableNameFilterType::Contain,
86 name_filter_str: String::new(),
87 name_filter_case_insensitive: true,
88
89 include_inputs: true,
90 include_outputs: true,
91 include_inouts: true,
92 include_others: true,
93
94 group_by_direction: false,
95 cache: RefCell::new(Default::default()),
96 }
97 }
98
99 fn name_filter_fn(&self) -> Box<dyn FnMut(&str) -> bool> {
100 if self.name_filter_str.is_empty() {
101 if self.name_filter_type == VariableNameFilterType::Regex {
102 let mut cache = self.cache.borrow_mut();
104 cache.regex_pattern = None;
105 cache.regex = None;
106 cache.regex_error = None;
107 }
108 return Box::new(|_var_name| true);
109 }
110
111 let filter_type = &self.name_filter_type;
113 let filter_str = self.name_filter_str.clone();
114 let case_insensitive = self.name_filter_case_insensitive;
115
116 let mut owned_regex: Option<Regex> = None;
118
119 if *filter_type != VariableNameFilterType::Fuzzy
120 {
122 let mut cache = self.cache.borrow_mut();
123
124 let pat = match filter_type {
125 VariableNameFilterType::Regex => filter_str.clone(),
126 VariableNameFilterType::Start => format!("^{}", escape(&filter_str)),
127 VariableNameFilterType::Contain => escape(&filter_str),
128 VariableNameFilterType::Fuzzy => unreachable!(),
129 };
130 let rebuild = (cache.regex_pattern.as_ref() != Some(&pat))
131 || cache.regex_case_insensitive != case_insensitive
132 || cache.regex.is_none();
133
134 if rebuild {
135 cache.regex_pattern = Some(pat.clone());
136 cache.regex_case_insensitive = case_insensitive;
137 match RegexBuilder::new(&pat)
138 .case_insensitive(case_insensitive)
139 .build()
140 {
141 Ok(r) => {
142 cache.regex = Some(r);
143 cache.regex_error = None;
144 }
145 Err(e) => {
146 cache.regex = None;
147 cache.regex_error = Some(e.to_string());
148 }
149 }
150 }
151
152 if let Some(r) = cache.regex.as_ref() {
153 owned_regex = Some(r.clone());
154 }
155 } match filter_type {
159 VariableNameFilterType::Fuzzy => {
160 let mut matcher = SkimMatcherV2::default();
161 matcher = if case_insensitive {
162 matcher.ignore_case()
163 } else {
164 matcher.respect_case()
165 };
166 let pat = filter_str;
167 Box::new(move |var_name| matcher.fuzzy_match(var_name, &pat).is_some())
168 }
169 VariableNameFilterType::Regex
170 | VariableNameFilterType::Start
171 | VariableNameFilterType::Contain => {
172 if let Some(regex) = owned_regex {
173 Box::new(move |var_name| regex.is_match(var_name))
174 } else {
175 Box::new(|_var_name| false)
176 }
177 }
178 }
179 }
180
181 fn kind_filter(&self, vr: &VariableRef, wave_container_opt: Option<&WaveContainer>) -> bool {
182 match get_variable_direction(vr, wave_container_opt) {
183 VariableDirection::Input => self.include_inputs,
184 VariableDirection::Output => self.include_outputs,
185 VariableDirection::InOut => self.include_inouts,
186 _ => self.include_others,
187 }
188 }
189
190 fn matching_variables(
191 &self,
192 variables: &[VariableRef],
193 wave_container_opt: Option<&WaveContainer>,
194 full_path: bool,
195 ) -> Vec<VariableRef> {
196 let mut name_filter = self.name_filter_fn();
197 if full_path {
198 variables
199 .iter()
200 .filter(|&vr| self.kind_filter(vr, wave_container_opt))
201 .filter(|&vr| name_filter(&vr.full_path_string()))
202 .cloned()
203 .collect_vec()
204 } else {
205 variables
206 .iter()
207 .filter(|&vr| self.kind_filter(vr, wave_container_opt))
208 .filter(|&vr| name_filter(&vr.name))
209 .cloned()
210 .collect_vec()
211 }
212 }
213
214 fn is_regex_and_invalid(&self) -> bool {
217 if self.name_filter_type != VariableNameFilterType::Regex {
218 return false;
219 }
220 let cache = self.cache.borrow();
221 cache.regex_error.is_some()
222 }
223
224 fn regex_error(&self) -> Option<String> {
227 if self.name_filter_type != VariableNameFilterType::Regex {
228 return None;
229 }
230 let cache = self.cache.borrow();
231 cache.regex_error.clone()
232 }
233}
234
235impl SystemState {
236 pub(crate) fn draw_variable_filter_edit(
237 &mut self,
238 ui: &mut Ui,
239 msgs: &mut Vec<Message>,
240 full_path: bool,
241 ) {
242 ui.with_layout(Layout::top_down(Align::LEFT), |ui| {
243 CollapsingState::load_with_default_open(
244 ui.ctx(),
245 ui.make_persistent_id("variable_filter"),
246 false,
247 )
248 .show_header(ui, |ui| {
249 ui.with_layout(Layout::right_to_left(Align::TOP), |ui| {
250 let default_padding = ui.spacing().button_padding;
251 ui.spacing_mut().button_padding = Vec2 {
252 x: 0.,
253 y: default_padding.y,
254 };
255 if ui
256 .button(icons::ADD_FILL)
257 .on_hover_text("Add all variables from active Scope")
258 .clicked()
259 {
260 self.add_filtered_variables(msgs, full_path);
261 }
262 if ui
263 .add_enabled(
264 !self.user.variable_filter.name_filter_str.is_empty(),
265 Button::new(icons::CLOSE_FILL),
266 )
267 .on_hover_text("Clear filter")
268 .clicked()
269 {
270 self.user.variable_filter.name_filter_str.clear();
271 }
272
273 let is_invalid = self.user.variable_filter.is_regex_and_invalid();
275 let error_msg = self.user.variable_filter.regex_error();
276
277 let original_bg = ui.style().visuals.extreme_bg_color;
279
280 if is_invalid {
281 ui.style_mut().visuals.extreme_bg_color =
282 self.user.config.theme.accent_error.background;
283 }
284
285 let mut response = ui.add(
286 TextEdit::singleline(&mut self.user.variable_filter.name_filter_str)
287 .hint_text("Filter"),
288 );
289
290 ui.style_mut().visuals.extreme_bg_color = original_bg;
292
293 if let Some(err) = error_msg {
295 response = response.on_hover_ui(|ui| {
296 ui.label("Invalid regex:");
297 ui.label(RichText::new(err).family(epaint::FontFamily::Monospace));
299 });
300 }
301
302 let request_focus = *self
304 .text_edit_request_focus
305 .get(VARIABLE_FILTER_ID)
306 .unwrap_or(&false);
307 if request_focus && !response.has_focus() {
308 response.request_focus();
309 msgs.push(Message::SetRequestTextEditFocus(
310 VARIABLE_FILTER_ID.to_string(),
311 false,
312 ));
313 }
314
315 if response.gained_focus() {
317 msgs.push(Message::SetTextEditFocused(
318 VARIABLE_FILTER_ID.to_string(),
319 true,
320 ));
321 }
322 if response.lost_focus() {
323 msgs.push(Message::SetTextEditFocused(
324 VARIABLE_FILTER_ID.to_string(),
325 false,
326 ));
327 }
328 ui.spacing_mut().button_padding = default_padding;
329 });
330 })
331 .body(|ui| self.variable_filter_type_menu(ui, msgs));
332 });
333 }
334
335 fn add_filtered_variables(&mut self, msgs: &mut Vec<Message>, full_path: bool) {
336 if let Some(waves) = self.user.waves.as_ref() {
337 if full_path {
338 let variables = waves.inner.as_waves().unwrap().variables();
339 msgs.push(Message::AddVariables(
340 self.filtered_variables(&variables, false),
341 ));
342 } else {
343 if let Some(active_scope) = waves.active_scope.as_ref() {
347 match active_scope {
348 ScopeType::WaveScope(active_scope) => {
349 let variables = waves
350 .inner
351 .as_waves()
352 .unwrap()
353 .variables_in_scope(active_scope);
354 msgs.push(Message::AddVariables(
355 self.filtered_variables(&variables, false),
356 ));
357 }
358 ScopeType::StreamScope(active_scope) => {
359 if let Transactions(inner) = &waves.inner {
360 match active_scope {
361 StreamScopeRef::Root => {
362 for stream in inner.get_streams() {
363 msgs.push(Message::AddStreamOrGenerator(
364 TransactionStreamRef::new_stream(
365 stream.id,
366 stream.name.clone(),
367 ),
368 ));
369 }
370 }
371 StreamScopeRef::Stream(s) => {
372 for gen_id in
373 &inner.get_stream(s.stream_id).unwrap().generators
374 {
375 let generator = inner.get_generator(*gen_id).unwrap();
376
377 msgs.push(Message::AddStreamOrGenerator(
378 TransactionStreamRef::new_gen(
379 generator.stream_id,
380 generator.id,
381 generator.name.clone(),
382 ),
383 ));
384 }
385 }
386 StreamScopeRef::Empty(_) => {}
387 }
388 }
389 }
390 }
391 }
392 }
393 }
394 }
395
396 fn variable_filter_type_menu(&self, ui: &mut Ui, msgs: &mut Vec<Message>) {
397 let mut name_filter_case_insensitive =
402 self.user.variable_filter.name_filter_case_insensitive;
403
404 if ui
405 .checkbox(&mut name_filter_case_insensitive, "Case insensitive")
406 .clicked()
407 {
408 msgs.push(Message::SetVariableNameFilterCaseInsensitive(
409 !self.user.variable_filter.name_filter_case_insensitive,
410 ));
411 }
412
413 ui.separator();
414
415 for filter_type in enum_iterator::all::<VariableNameFilterType>() {
416 if ui
417 .radio(
418 self.user.variable_filter.name_filter_type == filter_type,
419 filter_type.to_string(),
420 )
421 .clicked()
422 {
423 msgs.push(Message::SetVariableNameFilterType(filter_type));
424 }
425 }
426
427 ui.separator();
428
429 let mut group_by_direction = self.user.variable_filter.group_by_direction;
434
435 if ui
436 .checkbox(&mut group_by_direction, "Group by direction")
437 .clicked()
438 {
439 msgs.push(Message::SetVariableGroupByDirection(
440 !self.user.variable_filter.group_by_direction,
441 ));
442 }
443
444 ui.separator();
445
446 ui.horizontal(|ui| {
447 let input = VariableDirection::Input;
448 let output = VariableDirection::Output;
449 let inout = VariableDirection::InOut;
450
451 if ui
452 .add(
453 Button::new(input.get_icon().unwrap())
454 .selected(self.user.variable_filter.include_inputs),
455 )
456 .on_hover_text("Show inputs")
457 .clicked()
458 {
459 msgs.push(Message::SetVariableIOFilter(
460 VariableIOFilterType::Input,
461 !self.user.variable_filter.include_inputs,
462 ));
463 }
464
465 if ui
466 .add(
467 Button::new(output.get_icon().unwrap())
468 .selected(self.user.variable_filter.include_outputs),
469 )
470 .on_hover_text("Show outputs")
471 .clicked()
472 {
473 msgs.push(Message::SetVariableIOFilter(
474 VariableIOFilterType::Output,
475 !self.user.variable_filter.include_outputs,
476 ));
477 }
478
479 if ui
480 .add(
481 Button::new(inout.get_icon().unwrap())
482 .selected(self.user.variable_filter.include_inouts),
483 )
484 .on_hover_text("Show inouts")
485 .clicked()
486 {
487 msgs.push(Message::SetVariableIOFilter(
488 VariableIOFilterType::InOut,
489 !self.user.variable_filter.include_inouts,
490 ));
491 }
492
493 if ui
494 .add(
495 Button::new(icons::GLOBAL_LINE)
496 .selected(self.user.variable_filter.include_others),
497 )
498 .on_hover_text("Show others")
499 .clicked()
500 {
501 msgs.push(Message::SetVariableIOFilter(
502 VariableIOFilterType::Other,
503 !self.user.variable_filter.include_others,
504 ));
505 }
506 });
507 }
508
509 pub fn variable_cmp(
510 &self,
511 a: &VariableRef,
512 b: &VariableRef,
513 wave_container: Option<&WaveContainer>,
514 ) -> Ordering {
515 if !self.user.variable_filter.group_by_direction {
517 return numeric_sort::cmp(&a.name, &b.name);
518 }
519
520 let a_direction = get_variable_direction(a, wave_container);
521 let b_direction = get_variable_direction(b, wave_container);
522
523 if a_direction == b_direction {
524 numeric_sort::cmp(&a.name, &b.name)
525 } else if a_direction < b_direction {
526 Ordering::Less
527 } else {
528 Ordering::Greater
529 }
530 }
531
532 pub(crate) fn filtered_variables(
533 &self,
534 variables: &[VariableRef],
535 full_path: bool,
536 ) -> Vec<VariableRef> {
537 let wave_container = match &self.user.waves {
538 Some(wd) => wd.inner.as_waves(),
539 None => None,
540 };
541
542 self.user
543 .variable_filter
544 .matching_variables(variables, wave_container, full_path)
545 .iter()
546 .sorted_by(|a, b| self.variable_cmp(a, b, wave_container))
547 .cloned()
548 .collect_vec()
549 }
550
551 pub(crate) fn filtered_variables_unsorted(
554 &self,
555 variables: &[VariableRef],
556 full_path: bool,
557 ) -> Vec<VariableRef> {
558 let wave_container = match &self.user.waves {
559 Some(wd) => wd.inner.as_waves(),
560 None => None,
561 };
562
563 self.user
564 .variable_filter
565 .matching_variables(variables, wave_container, full_path)
566 .clone()
567 }
568}
569
570fn get_variable_direction(
571 vr: &VariableRef,
572 wave_container_opt: Option<&WaveContainer>,
573) -> VariableDirection {
574 match wave_container_opt {
575 Some(wave_container) => wave_container
576 .variable_meta(vr)
577 .map_or(VariableDirection::Unknown, |m| {
578 m.direction.unwrap_or(VariableDirection::Unknown)
579 }),
580 None => VariableDirection::Unknown,
581 }
582}
583
584#[cfg(test)]
585mod tests {
586 use super::*;
587
588 #[test]
589 fn test_empty_filter_matches_all() {
590 let filter = VariableFilter::new();
591 assert!(filter.name_filter_str.is_empty());
592
593 let mut filter_fn = filter.name_filter_fn();
594 assert!(filter_fn("test"));
596 assert!(filter_fn("anything"));
597 assert!(filter_fn(""));
598 }
599
600 #[test]
601 fn test_contain_filter_basic() {
602 let mut filter = VariableFilter::new();
603 filter.name_filter_type = VariableNameFilterType::Contain;
604 filter.name_filter_str = "clock".to_string();
605 filter.name_filter_case_insensitive = false;
606
607 let mut filter_fn = filter.name_filter_fn();
608 assert!(filter_fn("clock"));
609 assert!(filter_fn("my_clock"));
610 assert!(filter_fn("clock_signal"));
611 assert!(filter_fn("sys_clock_div"));
612 assert!(!filter_fn("clk"));
613 assert!(!filter_fn("CLOCK")); }
615
616 #[test]
617 fn test_contain_filter_case_insensitive() {
618 let mut filter = VariableFilter::new();
619 filter.name_filter_type = VariableNameFilterType::Contain;
620 filter.name_filter_str = "Clock".to_string();
621 filter.name_filter_case_insensitive = true;
622
623 let mut filter_fn = filter.name_filter_fn();
624 assert!(filter_fn("clock"));
625 assert!(filter_fn("CLOCK"));
626 assert!(filter_fn("ClOcK"));
627 assert!(filter_fn("my_Clock_signal"));
628 assert!(!filter_fn("clk"));
629 }
630
631 #[test]
632 fn test_start_filter() {
633 let mut filter = VariableFilter::new();
634 filter.name_filter_type = VariableNameFilterType::Start;
635 filter.name_filter_str = "sys".to_string();
636 filter.name_filter_case_insensitive = false;
637
638 let mut filter_fn = filter.name_filter_fn();
639 assert!(filter_fn("sys"));
640 assert!(filter_fn("sys_clock"));
641 assert!(filter_fn("system"));
642 assert!(!filter_fn("my_sys"));
643 assert!(!filter_fn("SYS")); }
645
646 #[test]
647 fn test_start_filter_case_insensitive() {
648 let mut filter = VariableFilter::new();
649 filter.name_filter_type = VariableNameFilterType::Start;
650 filter.name_filter_str = "Sys".to_string();
651 filter.name_filter_case_insensitive = true;
652
653 let mut filter_fn = filter.name_filter_fn();
654 assert!(filter_fn("sys"));
655 assert!(filter_fn("SYS_CLOCK"));
656 assert!(filter_fn("System"));
657 assert!(!filter_fn("my_sys"));
658 }
659
660 #[test]
661 fn test_regex_filter_valid() {
662 let mut filter = VariableFilter::new();
663 filter.name_filter_type = VariableNameFilterType::Regex;
664 filter.name_filter_str = r"^clk_\d+$".to_string();
665 filter.name_filter_case_insensitive = false;
666
667 let mut filter_fn = filter.name_filter_fn();
668 assert!(filter_fn("clk_0"));
669 assert!(filter_fn("clk_123"));
670 assert!(!filter_fn("clk_"));
671 assert!(!filter_fn("clk_abc"));
672 assert!(!filter_fn("my_clk_0"));
673 }
674
675 #[test]
676 fn test_regex_filter_invalid() {
677 let mut filter = VariableFilter::new();
678 filter.name_filter_type = VariableNameFilterType::Regex;
679 filter.name_filter_str = "[invalid(".to_string(); filter.name_filter_case_insensitive = false;
681
682 let mut filter_fn = filter.name_filter_fn();
684 assert!(!filter_fn("anything"));
685 assert!(!filter_fn("test"));
686
687 assert!(filter.is_regex_and_invalid());
689
690 let error = filter.regex_error();
692 assert!(error.is_some());
693 assert!(error.unwrap().contains("unclosed"));
694 }
695
696 #[test]
697 fn test_is_regex_and_invalid_only_for_regex_type() {
698 let mut filter = VariableFilter::new();
699 filter.name_filter_str = "[invalid(".to_string();
700
701 filter.name_filter_type = VariableNameFilterType::Contain;
703 let _ = filter.name_filter_fn();
705 assert!(!filter.is_regex_and_invalid());
706
707 filter.name_filter_type = VariableNameFilterType::Start;
708 let _ = filter.name_filter_fn();
710 assert!(!filter.is_regex_and_invalid());
711
712 filter.name_filter_type = VariableNameFilterType::Fuzzy;
713 let _ = filter.name_filter_fn();
715 assert!(!filter.is_regex_and_invalid());
716
717 filter.name_filter_type = VariableNameFilterType::Regex;
719 let _ = filter.name_filter_fn();
721 assert!(filter.is_regex_and_invalid());
722 }
723
724 #[test]
725 fn test_regex_error_only_for_regex_type() {
726 let mut filter = VariableFilter::new();
727 filter.name_filter_str = "[invalid(".to_string();
728
729 filter.name_filter_type = VariableNameFilterType::Regex;
731 let _ = filter.name_filter_fn();
732
733 filter.name_filter_type = VariableNameFilterType::Contain;
735 assert!(filter.regex_error().is_none());
736
737 filter.name_filter_type = VariableNameFilterType::Start;
738 assert!(filter.regex_error().is_none());
739
740 filter.name_filter_type = VariableNameFilterType::Regex;
742 assert!(filter.regex_error().is_some());
743 }
744
745 #[test]
746 fn test_fuzzy_filter() {
747 let mut filter = VariableFilter::new();
748 filter.name_filter_type = VariableNameFilterType::Fuzzy;
749 filter.name_filter_str = "clk".to_string();
750 filter.name_filter_case_insensitive = true;
751
752 let mut filter_fn = filter.name_filter_fn();
753 assert!(filter_fn("clock"));
755 assert!(filter_fn("c_l_k"));
756 assert!(filter_fn("call_lock"));
757 assert!(!filter_fn("kclc")); }
759
760 #[test]
761 fn test_special_chars_escaped_in_contain() {
762 let mut filter = VariableFilter::new();
763 filter.name_filter_type = VariableNameFilterType::Contain;
764 filter.name_filter_str = "sig[0]".to_string();
766 filter.name_filter_case_insensitive = false;
767
768 let mut filter_fn = filter.name_filter_fn();
769 assert!(filter_fn("sig[0]"));
770 assert!(filter_fn("my_sig[0]_data"));
771 assert!(!filter_fn("sig0")); assert!(!filter_fn("siga")); }
774
775 #[test]
776 fn test_special_chars_escaped_in_start() {
777 let mut filter = VariableFilter::new();
778 filter.name_filter_type = VariableNameFilterType::Start;
779 filter.name_filter_str = "data.value".to_string();
780 filter.name_filter_case_insensitive = false;
781
782 let mut filter_fn = filter.name_filter_fn();
783 assert!(filter_fn("data.value"));
784 assert!(filter_fn("data.value_out"));
785 assert!(!filter_fn("dataxvalue")); assert!(!filter_fn("my_data.value")); }
788
789 #[test]
790 fn test_cache_reuses_compiled_regex() {
791 let mut filter = VariableFilter::new();
792 filter.name_filter_type = VariableNameFilterType::Regex;
793 filter.name_filter_str = r"\d+".to_string();
794 filter.name_filter_case_insensitive = false;
795
796 let mut fn1 = filter.name_filter_fn();
798 assert!(fn1("123"));
799
800 let mut fn2 = filter.name_filter_fn();
802 assert!(fn2("456"));
803
804 let cache = filter.cache.borrow();
806 assert_eq!(cache.regex_pattern.as_ref().unwrap(), r"\d+");
807 assert!(cache.regex.is_some());
808 }
809
810 #[test]
811 fn test_cache_rebuilds_on_pattern_change() {
812 let mut filter = VariableFilter::new();
813 filter.name_filter_type = VariableNameFilterType::Contain;
814 filter.name_filter_str = "old".to_string();
815 filter.name_filter_case_insensitive = false;
816
817 let mut fn1 = filter.name_filter_fn();
818 assert!(fn1("old_value"));
819
820 filter.name_filter_str = "new".to_string();
822 let mut fn2 = filter.name_filter_fn();
823 assert!(fn2("new_value"));
824 assert!(!fn2("old_value"));
825 }
826
827 #[test]
828 fn test_cache_rebuilds_on_case_sensitivity_change() {
829 let mut filter = VariableFilter::new();
830 filter.name_filter_type = VariableNameFilterType::Contain;
831 filter.name_filter_str = "Test".to_string();
832 filter.name_filter_case_insensitive = false;
833
834 let mut fn1 = filter.name_filter_fn();
835 assert!(!fn1("test")); filter.name_filter_case_insensitive = true;
839 let mut fn2 = filter.name_filter_fn();
840 assert!(fn2("test")); }
842
843 #[test]
844 fn test_default_filter_settings() {
845 let filter = VariableFilter::new();
846
847 assert_eq!(filter.name_filter_type, VariableNameFilterType::Contain);
848 assert_eq!(filter.name_filter_str, "");
849 assert!(filter.name_filter_case_insensitive);
850
851 assert!(filter.include_inputs);
852 assert!(filter.include_outputs);
853 assert!(filter.include_inouts);
854 assert!(filter.include_others);
855
856 assert!(!filter.group_by_direction);
857 }
858}