Skip to content

Commit 8952b35

Browse files
committed
feat: highlight fields that match
1 parent 1ebb411 commit 8952b35

File tree

2 files changed

+68
-25
lines changed

2 files changed

+68
-25
lines changed

src/app.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -226,13 +226,17 @@ impl LogViewerApp {
226226
return;
227227
};
228228

229-
let Some(selected_values) =
230-
data.selected_row_data_as_slice(self.data_display_options.common_fields())
229+
let Some((selected_values, fields_matching_filter)) = data
230+
.selected_row_data_as_slice_with_filter_matching_fields(
231+
self.data_display_options.common_fields(),
232+
)
231233
else {
232234
ui.label("No row Selected");
233235
return;
234236
};
235237

238+
let color_matching_field = ui.visuals().strong_text_color();
239+
let color_normal_field = ui.visuals().text_color();
236240
let text_height = egui::TextStyle::Body
237241
.resolve(ui.style())
238242
.size
@@ -267,11 +271,16 @@ impl LogViewerApp {
267271
body.heterogeneous_rows(heights.iter().cloned(), |mut row| {
268272
let row_index = row.index();
269273
let (title, value) = &selected_values[row_index];
274+
let color = if fields_matching_filter.contains(&row_index) {
275+
color_matching_field
276+
} else {
277+
color_normal_field
278+
};
270279
row.col(|ui| {
271-
ui.label(title);
280+
ui.colored_label(color, title);
272281
});
273282
row.col(|ui| {
274-
ui.label(value.to_string());
283+
ui.colored_label(color, value.to_string());
275284
});
276285
});
277286
});

src/app/data.rs

Lines changed: 55 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,31 @@ impl Data {
166166
Some(self.rows[real_index].as_slice(common_fields))
167167
}
168168

169+
pub fn selected_row_data_as_slice_with_filter_matching_fields(
170+
&mut self,
171+
common_fields: &BTreeSet<String>,
172+
) -> Option<(&[(String, String)], Vec<usize>)> {
173+
// Collect other needed info before taking mutable borrow to appease the borrow checker (couldn't find another readable way)
174+
let is_filtered = self.is_filtered();
175+
let filter = if is_filtered {
176+
self.filter.clone()
177+
} else {
178+
None
179+
};
180+
let row_slice = self.selected_row_data_as_slice(common_fields)?;
181+
let matching_fields = if is_filtered {
182+
if let Some(filter) = filter.as_ref() {
183+
matching_fields(row_slice, filter).unwrap_or_default()
184+
} else {
185+
debug_assert!(false, "No filter but is_filtered is true?");
186+
Vec::new()
187+
}
188+
} else {
189+
Vec::new()
190+
};
191+
Some((row_slice, matching_fields))
192+
}
193+
169194
pub fn move_selected_to_next(&mut self) {
170195
let n = self.len();
171196
if let Some(selected) = self.selected_row.as_mut() {
@@ -229,7 +254,7 @@ impl Data {
229254
.iter_mut()
230255
.enumerate()
231256
.filter_map(|(i, row)| {
232-
if is_included(row, filter, common_fields) {
257+
if matching_fields(row.as_slice(common_fields), filter).is_some() {
233258
Some(i)
234259
} else {
235260
None
@@ -261,45 +286,54 @@ impl Data {
261286
}
262287
}
263288

264-
fn is_included(
265-
row: &mut LogRow,
266-
filter: &filter::FilterConfig,
267-
common_fields: &BTreeSet<String>,
268-
) -> bool {
289+
/// If the slice of fields and values matches the filter then the indices of the fields that match are returned or None if it does not match
290+
fn matching_fields(
291+
fields_and_values: &[(String, String)],
292+
filter: &FilterConfig,
293+
) -> Option<Vec<usize>> {
269294
let FilterConfig {
270295
search_key,
271296
filter_on,
272297
comparator,
273298
is_case_sensitive,
274299
} = filter;
275-
let fields_and_values = row.as_slice(common_fields);
276300
let search_key = if *is_case_sensitive {
277301
search_key
278302
} else {
279303
&search_key.to_lowercase()
280304
};
281-
let mut iter = fields_and_values.iter().map(|(k, v)| {
282-
if *is_case_sensitive {
283-
(Cow::Borrowed(k), Cow::Borrowed(v))
284-
} else {
285-
(Cow::Owned(k.to_lowercase()), Cow::Owned(v.to_lowercase()))
286-
}
287-
});
288-
289-
match filter_on {
290-
filter::FilterOn::Any => {
291-
iter.any(|(_, value)| comparator.apply(search_key, value.as_str()))
292-
}
305+
let iter = fields_and_values
306+
.iter()
307+
.map(|(k, v)| {
308+
if *is_case_sensitive {
309+
(Cow::Borrowed(k), Cow::Borrowed(v))
310+
} else {
311+
(Cow::Owned(k.to_lowercase()), Cow::Owned(v.to_lowercase()))
312+
}
313+
})
314+
.enumerate();
315+
let result: Vec<usize> = match filter_on {
316+
filter::FilterOn::Any => iter
317+
.filter_map(|(i, (_, value))| comparator.apply(search_key, value.as_str()).then_some(i))
318+
.collect(),
293319
filter::FilterOn::Field(FieldSpecifier { name }) => {
294320
let name = if *is_case_sensitive {
295321
name
296322
} else {
297323
&name.to_lowercase()
298324
};
299-
iter.any(|(field_name, value)| {
300-
name == field_name.as_str() && comparator.apply(search_key, value.as_str())
325+
iter.filter_map(|(i, (field_name, value))| {
326+
(name == field_name.as_str() && comparator.apply(search_key, value.as_str()))
327+
.then_some(i)
301328
})
329+
.collect()
302330
}
331+
};
332+
333+
if result.is_empty() {
334+
None
335+
} else {
336+
Some(result)
303337
}
304338
}
305339

0 commit comments

Comments
 (0)