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