From bd356a851b26c33d9d9af23294a5168a549af0b9 Mon Sep 17 00:00:00 2001 From: ClementTsang Date: Sun, 12 Jan 2020 18:45:11 -0500 Subject: [PATCH] Regex filter added. This is a very rudimentary implementation, but I feel it's good enough for now. --- README.md | 46 +++++++++++++++++++++++++++++++----------- src/app.rs | 25 ++++++++++++++++++++++- src/canvas.rs | 28 +++++++++++++++++-------- src/data_conversion.rs | 29 +++++++++++++++++++++++++- src/main.rs | 6 +++++- 5 files changed, 111 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 8678a15a..b0e3546e 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,30 @@ A graphical top clone, written in Rust. Inspired by both [gtop](https://github.c ![Quick demo recording](assets/recording_1.gif) +## Features + +Features of bottom include: + +- CPU widget to show a visual representation of per-core usage. Average CPU display also exists. + +- Memory widget to show a visual representation of both RAM and SWAP usage. + +- Networks widget to show a log-based visual representation of network usage. + +- Sortable and searchable process widget. Searching supports regex, and you can search by PID and process name. + +- Disks widget to display usage and I/O per second. + +- Temperature widget to monitor detected sensors in your system. + +The compatibility of each widget and operating systems are, as of version 0.1.0, as follows: + +| OS/Widget | CPU | Memory | Disks | Temperature | Processes | Networks | +| --------- | -------- | -------- | -------- | --------------------- | --------- | --------------------------------------------- | +| Linux | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| Windows | ✓ | ✓ | ✓ | Currently not working | ✓ | Partially supported (total RX/TX unavailable) | +| macOS | Untested | Untested | Untested | Untested | Untested | Untested | + ## Installation ### Linux @@ -24,16 +48,6 @@ You can install the in-development version by cloning and using `cargo build --r macOS support will hopefully come soonTM. -## Support - -The compatibility of each widget and operating systems are, as of version 0.1.0, as follows: - -| OS/Widget | CPU | Memory | Disks | Temperature | Processes | Networks | -| --------- | -------- | -------- | -------- | --------------------- | --------- | --------------------------------------------- | -| Linux | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| Windows | ✓ | ✓ | ✓ | Currently not working | ✓ | Partially supported (total RX/TX unavailable) | -| macOS | Untested | Untested | Untested | Untested | Untested | Untested | - ## Usage Run using `btm`. @@ -102,11 +116,19 @@ Run using `btm`. - `Tab` to group together processes with the same name. Disables PID sorting. `dd` will now kill all processes covered by that name. -- `Ctrl-f` or `/` to toggle a search box for finding a process. Press `Ctrl-p` or `Ctrl-n` within the search bar widget to switch between searching for PID and name respectively. Press `Esc` or `Ctrl-f` to close it. +- `Ctrl-f` or `/` to open the search widget. + +#### Search Widget + +- `Ctrl-p` or `Ctrl-n` to switch between searching for PID and name respectively. + +- `Esc` or `Ctrl-f` to close. + +Note that `q` is disabled while in the search widget. ### Mouse actions -- Scrolling with the mouse will scroll through the currently selected list, similar to using the up/down arrow keys. +- Scrolling with the mouse will scroll through the currently selected list. ## Thanks, kudos, and all the like diff --git a/src/app.rs b/src/app.rs index 075a5254..1993fb1e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -25,6 +25,11 @@ pub enum ScrollDirection { DOWN, } +lazy_static! { + static ref BASE_REGEX: std::result::Result = + regex::Regex::new(".*"); +} + pub struct App { // Sorting pub process_sorting_type: processes::ProcessSorting, @@ -61,6 +66,7 @@ pub struct App { enable_searching: bool, current_search_query: String, searching_pid: bool, + current_regex: std::result::Result, } impl App { @@ -103,6 +109,7 @@ impl App { enable_searching: false, current_search_query: String::default(), searching_pid: false, + current_regex: BASE_REGEX.clone(), //TODO: [OPT] seems like a thing we can switch to lt for if not fast } } @@ -214,7 +221,7 @@ impl App { self.searching_pid } - pub fn get_current_search_phrase(&self) -> &String { + pub fn get_current_search_query(&self) -> &String { &self.current_search_query } @@ -232,6 +239,18 @@ impl App { self.show_dd = false; } } + } else if let ApplicationPosition::ProcessSearch = self.current_application_position { + // Generate regex. + + // TODO: [OPT] if we can get this to work WITHOUT pressing enter that would be good. + // However, this will be a bit hard without a thorough look at optimization to avoid + // wasteful regex generation. + + self.current_regex = if self.current_search_query.is_empty() { + BASE_REGEX.clone() + } else { + regex::Regex::new(&(self.current_search_query)) + }; } } @@ -241,6 +260,10 @@ impl App { } } + pub fn get_current_regex_matcher(&self) -> &std::result::Result { + &self.current_regex + } + pub fn on_char_key(&mut self, caught_char: char) { // Forbid any char key presses when showing a dialog box... if !self.is_in_dialog() { diff --git a/src/canvas.rs b/src/canvas.rs index 417b4835..330f7767 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -47,7 +47,7 @@ lazy_static! { Text::raw("p to sort by PID.\n"), Text::raw("n to sort by process name.\n"), Text::raw("Tab to group together processes with the same name.\n"), - Text::raw("Ctrl-f or / to toggle searching for a process. Use Ctrl-p and Ctrl-n to toggle between searching for PID and name.\n"), + Text::raw("Ctrl-f to toggle searching for a process. / to just open it. Use Ctrl-p and Ctrl-n to toggle between searching for PID and name.\n"), Text::raw("\nFor startup flags, type in \"btm -h\".") ]; static ref COLOUR_LIST: Vec = gen_n_colours(constants::NUM_COLOURS); @@ -838,27 +838,39 @@ fn draw_disk_table( fn draw_search_field( f: &mut Frame, app_state: &mut app::App, draw_loc: Rect, ) { + let width = draw_loc.width - 10; + let query = app_state.get_current_search_query(); + let shrunk_query = if query.len() < width as usize { + query + } else { + &query[(query.len() - width as usize)..] + }; + let search_text = [ if app_state.is_searching_with_pid() { - Text::styled("\nPID: ", Style::default().fg(TABLE_HEADER_COLOUR)) + Text::styled("\nPID : ", Style::default().fg(TABLE_HEADER_COLOUR)) } else { Text::styled("\nName: ", Style::default().fg(TABLE_HEADER_COLOUR)) }, - Text::raw(app_state.get_current_search_phrase()), + Text::raw(shrunk_query), ]; Paragraph::new(search_text.iter()) .block( Block::default() - .title("Search (Ctrl-p and Ctrl-n to switch search types, Esc or Ctrl-f to close)") + .title("Search (Ctrl-p and Ctrl-n to switch search types, Esc or Ctrl-f to close, Enter to search)") .borders(Borders::ALL) - .border_style(match app_state.current_application_position { - app::ApplicationPosition::ProcessSearch => *CANVAS_HIGHLIGHTED_BORDER_STYLE, - _ => *CANVAS_BORDER_STYLE, + .border_style(if app_state.get_current_regex_matcher().is_err() { + Style::default().fg(Color::Red) + } else { + match app_state.current_application_position { + app::ApplicationPosition::ProcessSearch => *CANVAS_HIGHLIGHTED_BORDER_STYLE, + _ => *CANVAS_BORDER_STYLE, + } }), ) .style(Style::default().fg(Color::Gray)) .alignment(Alignment::Left) - .wrap(true) // TODO: We want this to keep going right... slicing? + .wrap(false) .render(f, draw_loc); } diff --git a/src/data_conversion.rs b/src/data_conversion.rs index b68d18fa..1d09b1b4 100644 --- a/src/data_conversion.rs +++ b/src/data_conversion.rs @@ -1,9 +1,13 @@ +//! This mainly concerns converting collected data into things that the canvas +//! can actually handle. + use crate::{ app::data_collection, constants, utils::gen_util::{get_exact_byte_values, get_simple_byte_values}, }; use constants::*; +use regex::Regex; #[derive(Default, Debug)] pub struct ConvertedNetworkData { @@ -137,11 +141,23 @@ pub fn update_disk_row(app_data: &data_collection::Data) -> Vec> { } pub fn update_process_row( - app_data: &data_collection::Data, + app_data: &data_collection::Data, regex_matcher: &std::result::Result, + use_pid: bool, ) -> (Vec, Vec) { let process_vector: Vec = app_data .list_of_processes .iter() + .filter(|process| { + if let Ok(matcher) = regex_matcher { + if use_pid { + matcher.is_match(&process.pid.to_string()) + } else { + matcher.is_match(&process.name) + } + } else { + true + } + }) .map(|process| ConvertedProcessData { pid: process.pid, name: process.name.to_string(), @@ -168,6 +184,17 @@ pub fn update_process_row( if let Some(grouped_list_of_processes) = &app_data.grouped_list_of_processes { grouped_process_vector = grouped_list_of_processes .iter() + .filter(|process| { + if let Ok(matcher) = regex_matcher { + if use_pid { + matcher.is_match(&process.pid.to_string()) + } else { + matcher.is_match(&process.name) + } + } else { + true + } + }) .map(|process| ConvertedProcessData { pid: process.pid, name: process.name.to_string(), diff --git a/src/main.rs b/src/main.rs index ef346e78..506707bb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -384,7 +384,11 @@ fn handle_process_sorting(app: &mut app::App) { app.process_sorting_reverse, ); - let tuple_results = update_process_row(&app.data); + let tuple_results = update_process_row( + &app.data, + app.get_current_regex_matcher(), + app.is_searching_with_pid(), + ); app.canvas_data.process_data = tuple_results.0; app.canvas_data.grouped_process_data = tuple_results.1; }