mirror of
https://github.com/ClementTsang/bottom.git
synced 2025-07-27 07:34:27 +02:00
Regex filter added. This is a very rudimentary implementation, but I feel it's good enough for now.
This commit is contained in:
parent
2bb1333d04
commit
bd356a851b
46
README.md
46
README.md
@ -6,6 +6,30 @@ A graphical top clone, written in Rust. Inspired by both [gtop](https://github.c
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
## 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
|
## Installation
|
||||||
|
|
||||||
### Linux
|
### Linux
|
||||||
@ -24,16 +48,6 @@ You can install the in-development version by cloning and using `cargo build --r
|
|||||||
|
|
||||||
macOS support will hopefully come soon<sup>TM</sup>.
|
macOS support will hopefully come soon<sup>TM</sup>.
|
||||||
|
|
||||||
## 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
|
## Usage
|
||||||
|
|
||||||
Run using `btm`.
|
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.
|
- `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
|
### 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
|
## Thanks, kudos, and all the like
|
||||||
|
|
||||||
|
25
src/app.rs
25
src/app.rs
@ -25,6 +25,11 @@ pub enum ScrollDirection {
|
|||||||
DOWN,
|
DOWN,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref BASE_REGEX: std::result::Result<regex::Regex, regex::Error> =
|
||||||
|
regex::Regex::new(".*");
|
||||||
|
}
|
||||||
|
|
||||||
pub struct App {
|
pub struct App {
|
||||||
// Sorting
|
// Sorting
|
||||||
pub process_sorting_type: processes::ProcessSorting,
|
pub process_sorting_type: processes::ProcessSorting,
|
||||||
@ -61,6 +66,7 @@ pub struct App {
|
|||||||
enable_searching: bool,
|
enable_searching: bool,
|
||||||
current_search_query: String,
|
current_search_query: String,
|
||||||
searching_pid: bool,
|
searching_pid: bool,
|
||||||
|
current_regex: std::result::Result<regex::Regex, regex::Error>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
@ -103,6 +109,7 @@ impl App {
|
|||||||
enable_searching: false,
|
enable_searching: false,
|
||||||
current_search_query: String::default(),
|
current_search_query: String::default(),
|
||||||
searching_pid: false,
|
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
|
self.searching_pid
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_current_search_phrase(&self) -> &String {
|
pub fn get_current_search_query(&self) -> &String {
|
||||||
&self.current_search_query
|
&self.current_search_query
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,6 +239,18 @@ impl App {
|
|||||||
self.show_dd = false;
|
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<regex::Regex, regex::Error> {
|
||||||
|
&self.current_regex
|
||||||
|
}
|
||||||
|
|
||||||
pub fn on_char_key(&mut self, caught_char: char) {
|
pub fn on_char_key(&mut self, caught_char: char) {
|
||||||
// Forbid any char key presses when showing a dialog box...
|
// Forbid any char key presses when showing a dialog box...
|
||||||
if !self.is_in_dialog() {
|
if !self.is_in_dialog() {
|
||||||
|
@ -47,7 +47,7 @@ lazy_static! {
|
|||||||
Text::raw("p to sort by PID.\n"),
|
Text::raw("p to sort by PID.\n"),
|
||||||
Text::raw("n to sort by process name.\n"),
|
Text::raw("n to sort by process name.\n"),
|
||||||
Text::raw("Tab to group together processes with the same 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\".")
|
Text::raw("\nFor startup flags, type in \"btm -h\".")
|
||||||
];
|
];
|
||||||
static ref COLOUR_LIST: Vec<Color> = gen_n_colours(constants::NUM_COLOURS);
|
static ref COLOUR_LIST: Vec<Color> = gen_n_colours(constants::NUM_COLOURS);
|
||||||
@ -838,27 +838,39 @@ fn draw_disk_table<B: backend::Backend>(
|
|||||||
fn draw_search_field<B: backend::Backend>(
|
fn draw_search_field<B: backend::Backend>(
|
||||||
f: &mut Frame<B>, app_state: &mut app::App, draw_loc: Rect,
|
f: &mut Frame<B>, 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 = [
|
let search_text = [
|
||||||
if app_state.is_searching_with_pid() {
|
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 {
|
} else {
|
||||||
Text::styled("\nName: ", Style::default().fg(TABLE_HEADER_COLOUR))
|
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())
|
Paragraph::new(search_text.iter())
|
||||||
.block(
|
.block(
|
||||||
Block::default()
|
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)
|
.borders(Borders::ALL)
|
||||||
.border_style(match app_state.current_application_position {
|
.border_style(if app_state.get_current_regex_matcher().is_err() {
|
||||||
app::ApplicationPosition::ProcessSearch => *CANVAS_HIGHLIGHTED_BORDER_STYLE,
|
Style::default().fg(Color::Red)
|
||||||
_ => *CANVAS_BORDER_STYLE,
|
} else {
|
||||||
|
match app_state.current_application_position {
|
||||||
|
app::ApplicationPosition::ProcessSearch => *CANVAS_HIGHLIGHTED_BORDER_STYLE,
|
||||||
|
_ => *CANVAS_BORDER_STYLE,
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.style(Style::default().fg(Color::Gray))
|
.style(Style::default().fg(Color::Gray))
|
||||||
.alignment(Alignment::Left)
|
.alignment(Alignment::Left)
|
||||||
.wrap(true) // TODO: We want this to keep going right... slicing?
|
.wrap(false)
|
||||||
.render(f, draw_loc);
|
.render(f, draw_loc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
|
//! This mainly concerns converting collected data into things that the canvas
|
||||||
|
//! can actually handle.
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::data_collection,
|
app::data_collection,
|
||||||
constants,
|
constants,
|
||||||
utils::gen_util::{get_exact_byte_values, get_simple_byte_values},
|
utils::gen_util::{get_exact_byte_values, get_simple_byte_values},
|
||||||
};
|
};
|
||||||
use constants::*;
|
use constants::*;
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct ConvertedNetworkData {
|
pub struct ConvertedNetworkData {
|
||||||
@ -137,11 +141,23 @@ pub fn update_disk_row(app_data: &data_collection::Data) -> Vec<Vec<String>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_process_row(
|
pub fn update_process_row(
|
||||||
app_data: &data_collection::Data,
|
app_data: &data_collection::Data, regex_matcher: &std::result::Result<Regex, regex::Error>,
|
||||||
|
use_pid: bool,
|
||||||
) -> (Vec<ConvertedProcessData>, Vec<ConvertedProcessData>) {
|
) -> (Vec<ConvertedProcessData>, Vec<ConvertedProcessData>) {
|
||||||
let process_vector: Vec<ConvertedProcessData> = app_data
|
let process_vector: Vec<ConvertedProcessData> = app_data
|
||||||
.list_of_processes
|
.list_of_processes
|
||||||
.iter()
|
.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 {
|
.map(|process| ConvertedProcessData {
|
||||||
pid: process.pid,
|
pid: process.pid,
|
||||||
name: process.name.to_string(),
|
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 {
|
if let Some(grouped_list_of_processes) = &app_data.grouped_list_of_processes {
|
||||||
grouped_process_vector = grouped_list_of_processes
|
grouped_process_vector = grouped_list_of_processes
|
||||||
.iter()
|
.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 {
|
.map(|process| ConvertedProcessData {
|
||||||
pid: process.pid,
|
pid: process.pid,
|
||||||
name: process.name.to_string(),
|
name: process.name.to_string(),
|
||||||
|
@ -384,7 +384,11 @@ fn handle_process_sorting(app: &mut app::App) {
|
|||||||
app.process_sorting_reverse,
|
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.process_data = tuple_results.0;
|
||||||
app.canvas_data.grouped_process_data = tuple_results.1;
|
app.canvas_data.grouped_process_data = tuple_results.1;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user