From 32d603ddc6e91669e591265f975036939dba8c66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Orhun=20Parmaks=C4=B1z?= Date: Wed, 21 Aug 2024 21:11:19 +0300 Subject: [PATCH] feat(tui): highlight search results --- Cargo.lock | 1 + Cargo.toml | 1 + src/tui/ui.rs | 87 +++++++++++++++++++++++++++++++++------------------ 3 files changed, 59 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ca5197a..3700376 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -196,6 +196,7 @@ dependencies = [ "console", "elf", "heh", + "itertools", "lddtree", "lurk-cli", "nix", diff --git a/Cargo.toml b/Cargo.toml index c8f06e0..d46aea2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ bytesize = "1.3.0" sysinfo = { version = "0.31.2", default-features = false, features = ["user"] } webbrowser = "1.0.1" lddtree = "0.3.5" +itertools = "0.13.0" [dev-dependencies] pretty_assertions = "1.4.0" diff --git a/src/tui/ui.rs b/src/tui/ui.rs index 658dfb3..ec83d67 100644 --- a/src/tui/ui.rs +++ b/src/tui/ui.rs @@ -553,12 +553,14 @@ pub fn render_static_analysis(state: &mut State, frame: &mut Frame, rect: Rect) Row::new(items.iter().enumerate().map(|(i, value)| { Cell::from(Line::from( if value.width() > max_row_width && i == items.len() - 1 { - vec![ + let mut spans = highlight_search_result( value.chars().take(max_row_width).collect::().into(), - "…".fg(Color::Rgb(100, 100, 100)), - ] + state, + ); + spans.push("…".fg(Color::Rgb(100, 100, 100))); + spans } else { - vec![value.to_string().into()] + highlight_search_result(value.to_string().into(), state) }, )) })) @@ -676,24 +678,25 @@ pub fn render_strings(state: &mut State, frame: &mut Frame, rect: Rect) { frame.render_stateful_widget( Table::new( items.map(|items| { - Row::new(vec![Cell::from(Line::from({ + Row::new(vec![Cell::from({ let index = format!("{:>p$}", items[0], p = left_padding); let value = items[1].to_string(); - let mut line = vec![index.clone().cyan(), " ".into()]; + let mut spans = vec![index.clone().cyan(), " ".into()]; if index.width() + value.width() > max_row_width { - line.push( + spans.extend(highlight_search_result( value .chars() .take(max_row_width.saturating_sub(index.width())) .collect::() .into(), - ); - line.push("…".fg(Color::Rgb(100, 100, 100))); + state, + )); + spans.push("…".fg(Color::Rgb(100, 100, 100))); } else { - line.push(value.into()); + spans.extend(highlight_search_result(value.into(), state)) } - line - }))]) + Line::from(spans) + })]) }), &[Constraint::Percentage(100)], ) @@ -728,7 +731,7 @@ pub fn render_strings(state: &mut State, frame: &mut Frame, rect: Rect) { ) .title_bottom(get_input_line(state)), ) - .highlight_style(Style::default().add_modifier(Modifier::BOLD)), + .highlight_style(Style::default().fg(Color::Green).bold()), rect, &mut list_state, ); @@ -856,26 +859,34 @@ pub fn render_dynamic_analysis(state: &mut State, frame: &mut Frame, rect: Rect) } frame.render_widget( - Paragraph::new(state.analyzer.system_calls.clone()) - .block( - Block::bordered() - .title(vec![ + Paragraph::new( + state + .analyzer + .system_calls + .clone() + .into_iter() + .map(|line| highlight_search_result(line, state).into()) + .collect::>(), + ) + .block( + Block::bordered() + .title(vec![ + "|".fg(Color::Rgb(100, 100, 100)), + "System Calls".white().bold(), + "|".fg(Color::Rgb(100, 100, 100)), + ]) + .title_bottom( + Line::from(vec![ "|".fg(Color::Rgb(100, 100, 100)), - "System Calls".white().bold(), + "Total: ".into(), + state.analyzer.system_calls.len().to_string().white().bold(), "|".fg(Color::Rgb(100, 100, 100)), ]) - .title_bottom( - Line::from(vec![ - "|".fg(Color::Rgb(100, 100, 100)), - "Total: ".into(), - state.analyzer.system_calls.len().to_string().white().bold(), - "|".fg(Color::Rgb(100, 100, 100)), - ]) - .right_aligned(), - ) - .title_bottom(get_input_line(state)), - ) - .scroll((state.scroll_index as u16, 0)), + .right_aligned(), + ) + .title_bottom(get_input_line(state)), + ) + .scroll((state.scroll_index as u16, 0)), rect, ); @@ -925,3 +936,19 @@ fn get_input_line<'a>(state: &'a State) -> Line<'a> { Line::default() } } + +/// Returns the line with the search result highlighted. +fn highlight_search_result<'a>(line: Line<'a>, state: &'a State) -> Vec> { + let line_str = line.to_string(); + if line_str.contains(state.input.value()) && !state.input.value().is_empty() { + let splits = line_str.split(state.input.value()); + let chunks = splits.into_iter().map(|c| Span::from(c.to_owned())); + let pattern = Span::styled( + state.input.value(), + Style::new().bg(Color::Yellow).fg(Color::Black), + ); + itertools::intersperse(chunks, pattern).collect::>() + } else { + line.spans.clone() + } +}