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::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(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 pub 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().join(".")))
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 pub 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 pub 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 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 pub 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 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
531fn get_variable_direction(
532 vr: &VariableRef,
533 wave_container_opt: Option<&WaveContainer>,
534) -> VariableDirection {
535 match wave_container_opt {
536 Some(wave_container) => wave_container
537 .variable_meta(vr)
538 .map_or(VariableDirection::Unknown, |m| {
539 m.direction.unwrap_or(VariableDirection::Unknown)
540 }),
541 None => VariableDirection::Unknown,
542 }
543}
544
545#[cfg(test)]
546mod tests {
547 use super::*;
548
549 #[test]
550 fn test_empty_filter_matches_all() {
551 let filter = VariableFilter::new();
552 assert!(filter.name_filter_str.is_empty());
553
554 let mut filter_fn = filter.name_filter_fn();
555 assert!(filter_fn("test"));
557 assert!(filter_fn("anything"));
558 assert!(filter_fn(""));
559 }
560
561 #[test]
562 fn test_contain_filter_basic() {
563 let mut filter = VariableFilter::new();
564 filter.name_filter_type = VariableNameFilterType::Contain;
565 filter.name_filter_str = "clock".to_string();
566 filter.name_filter_case_insensitive = false;
567
568 let mut filter_fn = filter.name_filter_fn();
569 assert!(filter_fn("clock"));
570 assert!(filter_fn("my_clock"));
571 assert!(filter_fn("clock_signal"));
572 assert!(filter_fn("sys_clock_div"));
573 assert!(!filter_fn("clk"));
574 assert!(!filter_fn("CLOCK")); }
576
577 #[test]
578 fn test_contain_filter_case_insensitive() {
579 let mut filter = VariableFilter::new();
580 filter.name_filter_type = VariableNameFilterType::Contain;
581 filter.name_filter_str = "Clock".to_string();
582 filter.name_filter_case_insensitive = true;
583
584 let mut filter_fn = filter.name_filter_fn();
585 assert!(filter_fn("clock"));
586 assert!(filter_fn("CLOCK"));
587 assert!(filter_fn("ClOcK"));
588 assert!(filter_fn("my_Clock_signal"));
589 assert!(!filter_fn("clk"));
590 }
591
592 #[test]
593 fn test_start_filter() {
594 let mut filter = VariableFilter::new();
595 filter.name_filter_type = VariableNameFilterType::Start;
596 filter.name_filter_str = "sys".to_string();
597 filter.name_filter_case_insensitive = false;
598
599 let mut filter_fn = filter.name_filter_fn();
600 assert!(filter_fn("sys"));
601 assert!(filter_fn("sys_clock"));
602 assert!(filter_fn("system"));
603 assert!(!filter_fn("my_sys"));
604 assert!(!filter_fn("SYS")); }
606
607 #[test]
608 fn test_start_filter_case_insensitive() {
609 let mut filter = VariableFilter::new();
610 filter.name_filter_type = VariableNameFilterType::Start;
611 filter.name_filter_str = "Sys".to_string();
612 filter.name_filter_case_insensitive = true;
613
614 let mut filter_fn = filter.name_filter_fn();
615 assert!(filter_fn("sys"));
616 assert!(filter_fn("SYS_CLOCK"));
617 assert!(filter_fn("System"));
618 assert!(!filter_fn("my_sys"));
619 }
620
621 #[test]
622 fn test_regex_filter_valid() {
623 let mut filter = VariableFilter::new();
624 filter.name_filter_type = VariableNameFilterType::Regex;
625 filter.name_filter_str = r"^clk_\d+$".to_string();
626 filter.name_filter_case_insensitive = false;
627
628 let mut filter_fn = filter.name_filter_fn();
629 assert!(filter_fn("clk_0"));
630 assert!(filter_fn("clk_123"));
631 assert!(!filter_fn("clk_"));
632 assert!(!filter_fn("clk_abc"));
633 assert!(!filter_fn("my_clk_0"));
634 }
635
636 #[test]
637 fn test_regex_filter_invalid() {
638 let mut filter = VariableFilter::new();
639 filter.name_filter_type = VariableNameFilterType::Regex;
640 filter.name_filter_str = "[invalid(".to_string(); filter.name_filter_case_insensitive = false;
642
643 let mut filter_fn = filter.name_filter_fn();
645 assert!(!filter_fn("anything"));
646 assert!(!filter_fn("test"));
647
648 assert!(filter.is_regex_and_invalid());
650
651 let error = filter.regex_error();
653 assert!(error.is_some());
654 assert!(error.unwrap().contains("unclosed"));
655 }
656
657 #[test]
658 fn test_is_regex_and_invalid_only_for_regex_type() {
659 let mut filter = VariableFilter::new();
660 filter.name_filter_str = "[invalid(".to_string();
661
662 filter.name_filter_type = VariableNameFilterType::Contain;
664 let _ = filter.name_filter_fn();
666 assert!(!filter.is_regex_and_invalid());
667
668 filter.name_filter_type = VariableNameFilterType::Start;
669 let _ = filter.name_filter_fn();
671 assert!(!filter.is_regex_and_invalid());
672
673 filter.name_filter_type = VariableNameFilterType::Fuzzy;
674 let _ = filter.name_filter_fn();
676 assert!(!filter.is_regex_and_invalid());
677
678 filter.name_filter_type = VariableNameFilterType::Regex;
680 let _ = filter.name_filter_fn();
682 assert!(filter.is_regex_and_invalid());
683 }
684
685 #[test]
686 fn test_regex_error_only_for_regex_type() {
687 let mut filter = VariableFilter::new();
688 filter.name_filter_str = "[invalid(".to_string();
689
690 filter.name_filter_type = VariableNameFilterType::Regex;
692 let _ = filter.name_filter_fn();
693
694 filter.name_filter_type = VariableNameFilterType::Contain;
696 assert!(filter.regex_error().is_none());
697
698 filter.name_filter_type = VariableNameFilterType::Start;
699 assert!(filter.regex_error().is_none());
700
701 filter.name_filter_type = VariableNameFilterType::Regex;
703 assert!(filter.regex_error().is_some());
704 }
705
706 #[test]
707 fn test_fuzzy_filter() {
708 let mut filter = VariableFilter::new();
709 filter.name_filter_type = VariableNameFilterType::Fuzzy;
710 filter.name_filter_str = "clk".to_string();
711 filter.name_filter_case_insensitive = true;
712
713 let mut filter_fn = filter.name_filter_fn();
714 assert!(filter_fn("clock"));
716 assert!(filter_fn("c_l_k"));
717 assert!(filter_fn("call_lock"));
718 assert!(!filter_fn("kclc")); }
720
721 #[test]
722 fn test_special_chars_escaped_in_contain() {
723 let mut filter = VariableFilter::new();
724 filter.name_filter_type = VariableNameFilterType::Contain;
725 filter.name_filter_str = "sig[0]".to_string();
727 filter.name_filter_case_insensitive = false;
728
729 let mut filter_fn = filter.name_filter_fn();
730 assert!(filter_fn("sig[0]"));
731 assert!(filter_fn("my_sig[0]_data"));
732 assert!(!filter_fn("sig0")); assert!(!filter_fn("siga")); }
735
736 #[test]
737 fn test_special_chars_escaped_in_start() {
738 let mut filter = VariableFilter::new();
739 filter.name_filter_type = VariableNameFilterType::Start;
740 filter.name_filter_str = "data.value".to_string();
741 filter.name_filter_case_insensitive = false;
742
743 let mut filter_fn = filter.name_filter_fn();
744 assert!(filter_fn("data.value"));
745 assert!(filter_fn("data.value_out"));
746 assert!(!filter_fn("dataxvalue")); assert!(!filter_fn("my_data.value")); }
749
750 #[test]
751 fn test_cache_reuses_compiled_regex() {
752 let mut filter = VariableFilter::new();
753 filter.name_filter_type = VariableNameFilterType::Regex;
754 filter.name_filter_str = r"\d+".to_string();
755 filter.name_filter_case_insensitive = false;
756
757 let mut fn1 = filter.name_filter_fn();
759 assert!(fn1("123"));
760
761 let mut fn2 = filter.name_filter_fn();
763 assert!(fn2("456"));
764
765 let cache = filter.cache.borrow();
767 assert_eq!(cache.regex_pattern.as_ref().unwrap(), r"\d+");
768 assert!(cache.regex.is_some());
769 }
770
771 #[test]
772 fn test_cache_rebuilds_on_pattern_change() {
773 let mut filter = VariableFilter::new();
774 filter.name_filter_type = VariableNameFilterType::Contain;
775 filter.name_filter_str = "old".to_string();
776 filter.name_filter_case_insensitive = false;
777
778 let mut fn1 = filter.name_filter_fn();
779 assert!(fn1("old_value"));
780
781 filter.name_filter_str = "new".to_string();
783 let mut fn2 = filter.name_filter_fn();
784 assert!(fn2("new_value"));
785 assert!(!fn2("old_value"));
786 }
787
788 #[test]
789 fn test_cache_rebuilds_on_case_sensitivity_change() {
790 let mut filter = VariableFilter::new();
791 filter.name_filter_type = VariableNameFilterType::Contain;
792 filter.name_filter_str = "Test".to_string();
793 filter.name_filter_case_insensitive = false;
794
795 let mut fn1 = filter.name_filter_fn();
796 assert!(!fn1("test")); filter.name_filter_case_insensitive = true;
800 let mut fn2 = filter.name_filter_fn();
801 assert!(fn2("test")); }
803
804 #[test]
805 fn test_default_filter_settings() {
806 let filter = VariableFilter::new();
807
808 assert_eq!(filter.name_filter_type, VariableNameFilterType::Contain);
809 assert_eq!(filter.name_filter_str, "");
810 assert!(filter.name_filter_case_insensitive);
811
812 assert!(filter.include_inputs);
813 assert!(filter.include_outputs);
814 assert!(filter.include_inouts);
815 assert!(filter.include_others);
816
817 assert!(!filter.group_by_direction);
818 }
819}