1use derive_more::Display;
3use egui::{Button, Layout, TextEdit, Ui};
4use egui_remixicon::icons;
5use emath::{Align, Vec2};
6use enum_iterator::Sequence;
7use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
8use itertools::Itertools;
9use regex::{escape, Regex, RegexBuilder};
10use serde::{Deserialize, Serialize};
11
12use crate::data_container::DataContainer::Transactions;
13use crate::transaction_container::{StreamScopeRef, TransactionStreamRef};
14use crate::variable_direction::VariableDirectionExt;
15use crate::wave_container::WaveContainer;
16use crate::wave_data::ScopeType;
17use crate::{message::Message, wave_container::VariableRef, SystemState};
18use surfer_translation_types::VariableDirection;
19
20use std::cmp::Ordering;
21
22#[derive(Debug, Display, PartialEq, Serialize, Deserialize, Sequence)]
23pub enum VariableNameFilterType {
24 #[display("Fuzzy")]
25 Fuzzy,
26
27 #[display("Regular expression")]
28 Regex,
29
30 #[display("Variable starts with")]
31 Start,
32
33 #[display("Variable contains")]
34 Contain,
35}
36
37#[derive(Debug, Serialize, Deserialize)]
38pub struct VariableFilter {
39 pub(crate) name_filter_type: VariableNameFilterType,
40 pub(crate) name_filter_str: String,
41 pub(crate) name_filter_case_insensitive: bool,
42
43 pub(crate) include_inputs: bool,
44 pub(crate) include_outputs: bool,
45 pub(crate) include_inouts: bool,
46 pub(crate) include_others: bool,
47
48 pub(crate) group_by_direction: bool,
49}
50
51#[derive(Debug, Deserialize)]
52pub enum VariableIOFilterType {
53 Input,
54 Output,
55 InOut,
56 Other,
57}
58
59impl Default for VariableFilter {
60 fn default() -> Self {
61 Self::new()
62 }
63}
64
65impl VariableFilter {
66 pub fn new() -> VariableFilter {
67 VariableFilter {
68 name_filter_type: VariableNameFilterType::Contain,
69 name_filter_str: String::from(""),
70 name_filter_case_insensitive: true,
71
72 include_inputs: true,
73 include_outputs: true,
74 include_inouts: true,
75 include_others: true,
76
77 group_by_direction: false,
78 }
79 }
80
81 fn name_filter_fn(&self) -> Box<dyn FnMut(&str) -> bool> {
82 if self.name_filter_str.is_empty() {
83 return Box::new(|_var_name| true);
84 }
85
86 match self.name_filter_type {
87 VariableNameFilterType::Fuzzy => {
88 let matcher = if self.name_filter_case_insensitive {
89 SkimMatcherV2::default().ignore_case()
90 } else {
91 SkimMatcherV2::default().respect_case()
92 };
93
94 let filter_str_clone = self.name_filter_str.clone();
96
97 Box::new(move |var_name| matcher.fuzzy_match(var_name, &filter_str_clone).is_some())
98 }
99 VariableNameFilterType::Regex => {
100 if let Ok(regex) = RegexBuilder::new(&self.name_filter_str)
101 .case_insensitive(self.name_filter_case_insensitive)
102 .build()
103 {
104 Box::new(move |var_name| regex.is_match(var_name))
105 } else {
106 Box::new(|_var_name| false)
107 }
108 }
109 VariableNameFilterType::Start => {
110 if let Ok(regex) = RegexBuilder::new(&format!("^{}", escape(&self.name_filter_str)))
111 .case_insensitive(self.name_filter_case_insensitive)
112 .build()
113 {
114 Box::new(move |var_name| regex.is_match(var_name))
115 } else {
116 Box::new(|_var_name| false)
117 }
118 }
119 VariableNameFilterType::Contain => {
120 if let Ok(regex) = RegexBuilder::new(&escape(&self.name_filter_str))
121 .case_insensitive(self.name_filter_case_insensitive)
122 .build()
123 {
124 Box::new(move |var_name| regex.is_match(var_name))
125 } else {
126 Box::new(|_var_name| false)
127 }
128 }
129 }
130 }
131
132 fn kind_filter(&self, vr: &VariableRef, wave_container_opt: Option<&WaveContainer>) -> bool {
133 match get_variable_direction(vr, wave_container_opt) {
134 VariableDirection::Input => self.include_inputs,
135 VariableDirection::Output => self.include_outputs,
136 VariableDirection::InOut => self.include_inouts,
137 _ => self.include_others,
138 }
139 }
140
141 pub fn matching_variables(
142 &self,
143 variables: &[VariableRef],
144 wave_container_opt: Option<&WaveContainer>,
145 ) -> Vec<VariableRef> {
146 let mut name_filter = self.name_filter_fn();
147
148 variables
149 .iter()
150 .filter(|&vr| name_filter(&vr.name))
151 .filter(|&vr| self.kind_filter(vr, wave_container_opt))
152 .cloned()
153 .collect_vec()
154 }
155}
156
157impl SystemState {
158 pub fn draw_variable_filter_edit(&mut self, ui: &mut Ui, msgs: &mut Vec<Message>) {
159 ui.with_layout(Layout::right_to_left(Align::TOP), |ui| {
160 let default_padding = ui.spacing().button_padding;
161 ui.spacing_mut().button_padding = Vec2 {
162 x: 0.,
163 y: default_padding.y,
164 };
165 ui.button(icons::ADD_FILL)
166 .on_hover_text("Add all variables from active Scope")
167 .clicked()
168 .then(|| {
169 if let Some(waves) = self.user.waves.as_ref() {
170 if let Some(active_scope) = waves.active_scope.as_ref() {
174 match active_scope {
175 ScopeType::WaveScope(active_scope) => {
176 let variables = waves
177 .inner
178 .as_waves()
179 .unwrap()
180 .variables_in_scope(active_scope);
181 msgs.push(Message::AddVariables(self.filtered_variables(
182 &variables,
183 &self.user.variable_filter,
184 )));
185 }
186 ScopeType::StreamScope(active_scope) => {
187 let Transactions(inner) = &waves.inner else {
188 return;
189 };
190 match active_scope {
191 StreamScopeRef::Root => {
192 for stream in inner.get_streams() {
193 msgs.push(Message::AddStreamOrGenerator(
194 TransactionStreamRef::new_stream(
195 stream.id,
196 stream.name.clone(),
197 ),
198 ));
199 }
200 }
201 StreamScopeRef::Stream(s) => {
202 for gen_id in
203 &inner.get_stream(s.stream_id).unwrap().generators
204 {
205 let gen = inner.get_generator(*gen_id).unwrap();
206
207 msgs.push(Message::AddStreamOrGenerator(
208 TransactionStreamRef::new_gen(
209 gen.stream_id,
210 gen.id,
211 gen.name.clone(),
212 ),
213 ));
214 }
215 }
216 StreamScopeRef::Empty(_) => {}
217 }
218 }
219 }
220 }
221 }
222 });
223 ui.add(
224 Button::new(icons::FONT_SIZE)
225 .selected(!self.user.variable_filter.name_filter_case_insensitive),
226 )
227 .on_hover_text("Case sensitive filter")
228 .clicked()
229 .then(|| {
230 msgs.push(Message::SetVariableNameFilterCaseInsensitive(
231 !self.user.variable_filter.name_filter_case_insensitive,
232 ));
233 });
234 ui.menu_button(icons::FILTER_FILL, |ui| {
235 self.variable_filter_type_menu(ui, msgs);
236 });
237 ui.add_enabled(
238 !self.user.variable_filter.name_filter_str.is_empty(),
239 Button::new(icons::CLOSE_FILL),
240 )
241 .on_hover_text("Clear filter")
242 .clicked()
243 .then(|| self.user.variable_filter.name_filter_str.clear());
244
245 if self.user.variable_filter.name_filter_type == VariableNameFilterType::Regex
247 && Regex::new(&self.user.variable_filter.name_filter_str).is_err()
248 {
249 ui.style_mut().visuals.extreme_bg_color =
250 self.user.config.theme.accent_error.background;
251 }
252 let response = ui.add(
254 TextEdit::singleline(&mut self.user.variable_filter.name_filter_str)
255 .hint_text("Filter (context menu for type)"),
256 );
257 response.context_menu(|ui| {
258 self.variable_filter_type_menu(ui, msgs);
259 });
260 if response.gained_focus() {
262 msgs.push(Message::SetFilterFocused(true));
263 }
264 if response.lost_focus() {
265 msgs.push(Message::SetFilterFocused(false));
266 }
267 ui.spacing_mut().button_padding = default_padding;
268 });
269 }
270
271 pub fn variable_filter_type_menu(&self, ui: &mut Ui, msgs: &mut Vec<Message>) {
272 for filter_type in enum_iterator::all::<VariableNameFilterType>() {
273 ui.radio(
274 self.user.variable_filter.name_filter_type == filter_type,
275 filter_type.to_string(),
276 )
277 .clicked()
278 .then(|| {
279 ui.close_menu();
280 msgs.push(Message::SetVariableNameFilterType(filter_type));
281 });
282 }
283
284 ui.separator();
285
286 let mut group_by_direction = self.user.variable_filter.group_by_direction;
291
292 ui.checkbox(&mut group_by_direction, "Group by direction")
293 .clicked()
294 .then(|| {
295 msgs.push(Message::SetVariableGroupByDirection(
296 !self.user.variable_filter.group_by_direction,
297 ))
298 });
299
300 ui.separator();
301
302 ui.horizontal(|ui| {
303 let input = VariableDirection::Input;
304 let output = VariableDirection::Output;
305 let inout = VariableDirection::InOut;
306
307 ui.add(
308 Button::new(input.get_icon().unwrap())
309 .selected(self.user.variable_filter.include_inputs),
310 )
311 .on_hover_text("Show inputs")
312 .clicked()
313 .then(|| {
314 msgs.push(Message::SetVariableIOFilter(
315 VariableIOFilterType::Input,
316 !self.user.variable_filter.include_inputs,
317 ));
318 });
319
320 ui.add(
321 Button::new(output.get_icon().unwrap())
322 .selected(self.user.variable_filter.include_outputs),
323 )
324 .on_hover_text("Show outputs")
325 .clicked()
326 .then(|| {
327 msgs.push(Message::SetVariableIOFilter(
328 VariableIOFilterType::Output,
329 !self.user.variable_filter.include_outputs,
330 ));
331 });
332
333 ui.add(
334 Button::new(inout.get_icon().unwrap())
335 .selected(self.user.variable_filter.include_inouts),
336 )
337 .on_hover_text("Show inouts")
338 .clicked()
339 .then(|| {
340 msgs.push(Message::SetVariableIOFilter(
341 VariableIOFilterType::InOut,
342 !self.user.variable_filter.include_inouts,
343 ));
344 });
345
346 ui.add(
347 Button::new(icons::GLOBAL_LINE).selected(self.user.variable_filter.include_others),
348 )
349 .on_hover_text("Show others")
350 .clicked()
351 .then(|| {
352 msgs.push(Message::SetVariableIOFilter(
353 VariableIOFilterType::Other,
354 !self.user.variable_filter.include_others,
355 ));
356 });
357 });
358 }
359
360 pub fn variable_cmp(
361 &self,
362 a: &VariableRef,
363 b: &VariableRef,
364 wave_container: Option<&WaveContainer>,
365 ) -> Ordering {
366 let a_direction = get_variable_direction(a, wave_container);
367 let b_direction = get_variable_direction(b, wave_container);
368
369 if !self.user.variable_filter.group_by_direction || a_direction == b_direction {
370 numeric_sort::cmp(&a.name, &b.name)
371 } else if a_direction < b_direction {
372 Ordering::Less
373 } else {
374 Ordering::Greater
375 }
376 }
377
378 pub fn filtered_variables(
379 &self,
380 variables: &[VariableRef],
381 variable_filter: &VariableFilter,
382 ) -> Vec<VariableRef> {
383 let wave_container = match &self.user.waves {
384 Some(wd) => wd.inner.as_waves(),
385 None => None,
386 };
387
388 variable_filter
389 .matching_variables(variables, wave_container)
390 .iter()
391 .sorted_by(|a, b| self.variable_cmp(a, b, wave_container))
392 .cloned()
393 .collect_vec()
394 }
395}
396
397fn get_variable_direction(
398 vr: &VariableRef,
399 wave_container_opt: Option<&WaveContainer>,
400) -> VariableDirection {
401 match wave_container_opt {
402 Some(wave_container) => wave_container
403 .variable_meta(vr)
404 .map_or(VariableDirection::Unknown, |m| {
405 m.direction.unwrap_or(VariableDirection::Unknown)
406 }),
407 None => VariableDirection::Unknown,
408 }
409}