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 msgs.push(Message::SetVariableNameFilterType(filter_type));
280 });
281 }
282
283 ui.separator();
284
285 let mut group_by_direction = self.user.variable_filter.group_by_direction;
290
291 ui.checkbox(&mut group_by_direction, "Group by direction")
292 .clicked()
293 .then(|| {
294 msgs.push(Message::SetVariableGroupByDirection(
295 !self.user.variable_filter.group_by_direction,
296 ))
297 });
298
299 ui.separator();
300
301 ui.horizontal(|ui| {
302 let input = VariableDirection::Input;
303 let output = VariableDirection::Output;
304 let inout = VariableDirection::InOut;
305
306 ui.add(
307 Button::new(input.get_icon().unwrap())
308 .selected(self.user.variable_filter.include_inputs),
309 )
310 .on_hover_text("Show inputs")
311 .clicked()
312 .then(|| {
313 msgs.push(Message::SetVariableIOFilter(
314 VariableIOFilterType::Input,
315 !self.user.variable_filter.include_inputs,
316 ));
317 });
318
319 ui.add(
320 Button::new(output.get_icon().unwrap())
321 .selected(self.user.variable_filter.include_outputs),
322 )
323 .on_hover_text("Show outputs")
324 .clicked()
325 .then(|| {
326 msgs.push(Message::SetVariableIOFilter(
327 VariableIOFilterType::Output,
328 !self.user.variable_filter.include_outputs,
329 ));
330 });
331
332 ui.add(
333 Button::new(inout.get_icon().unwrap())
334 .selected(self.user.variable_filter.include_inouts),
335 )
336 .on_hover_text("Show inouts")
337 .clicked()
338 .then(|| {
339 msgs.push(Message::SetVariableIOFilter(
340 VariableIOFilterType::InOut,
341 !self.user.variable_filter.include_inouts,
342 ));
343 });
344
345 ui.add(
346 Button::new(icons::GLOBAL_LINE).selected(self.user.variable_filter.include_others),
347 )
348 .on_hover_text("Show others")
349 .clicked()
350 .then(|| {
351 msgs.push(Message::SetVariableIOFilter(
352 VariableIOFilterType::Other,
353 !self.user.variable_filter.include_others,
354 ));
355 });
356 });
357 }
358
359 pub fn variable_cmp(
360 &self,
361 a: &VariableRef,
362 b: &VariableRef,
363 wave_container: Option<&WaveContainer>,
364 ) -> Ordering {
365 let a_direction = get_variable_direction(a, wave_container);
366 let b_direction = get_variable_direction(b, wave_container);
367
368 if !self.user.variable_filter.group_by_direction || a_direction == b_direction {
369 numeric_sort::cmp(&a.name, &b.name)
370 } else if a_direction < b_direction {
371 Ordering::Less
372 } else {
373 Ordering::Greater
374 }
375 }
376
377 pub fn filtered_variables(
378 &self,
379 variables: &[VariableRef],
380 variable_filter: &VariableFilter,
381 ) -> Vec<VariableRef> {
382 let wave_container = match &self.user.waves {
383 Some(wd) => wd.inner.as_waves(),
384 None => None,
385 };
386
387 variable_filter
388 .matching_variables(variables, wave_container)
389 .iter()
390 .sorted_by(|a, b| self.variable_cmp(a, b, wave_container))
391 .cloned()
392 .collect_vec()
393 }
394}
395
396fn get_variable_direction(
397 vr: &VariableRef,
398 wave_container_opt: Option<&WaveContainer>,
399) -> VariableDirection {
400 match wave_container_opt {
401 Some(wave_container) => wave_container
402 .variable_meta(vr)
403 .map_or(VariableDirection::Unknown, |m| {
404 m.direction.unwrap_or(VariableDirection::Unknown)
405 }),
406 None => VariableDirection::Unknown,
407 }
408}