mirror of
https://github.com/ClementTsang/bottom
synced 2024-11-25 13:40:20 +00:00
change: more advanced searching and filtering
This commit is contained in:
parent
bb45763b39
commit
6e81fbeebf
14 changed files with 675 additions and 282 deletions
|
@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.5.0] - Unreleased
|
||||
|
||||
### Features
|
||||
|
||||
- [#114](https://github.com/ClementTsang/bottom/pull/114): Process state per process (originally in 0.4.0, moved to later).
|
||||
|
||||
## [0.4.0] - Unreleased
|
||||
|
||||
### Features
|
||||
|
@ -13,8 +19,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
- [#55](https://github.com/ClementTsang/bottom/issues/55): Battery monitoring widget.
|
||||
|
||||
- [#114](https://github.com/ClementTsang/bottom/pull/114): Process state per process.
|
||||
|
||||
- [#134](https://github.com/ClementTsang/bottom/pull/134): `hjkl` movement to delete dialog (credit to [andys8](https://github.com/andys8)).
|
||||
|
||||
- [#59](https://github.com/ClementTsang/bottom/issues/59): `Alt-h` and `Alt-l` to move left/right in query (and rest of the app actually).
|
||||
|
|
|
@ -49,6 +49,9 @@ If you want to help contribute by submitting a PR, by all means, I'm open! In re
|
|||
|
||||
- You can check clippy using `cargo +nightly clippy`.
|
||||
|
||||
- You may notice that I have fern and log as dependencies; this is mostly for easy debugging via the `debug!()` macro. It writes to the
|
||||
`debug.log` file that will automatically be created if you run in debug mode (so `cargo run`).
|
||||
|
||||
And in regards to the pull request process:
|
||||
|
||||
- Create a personal fork of the process and PR that, as per the [fork and pull method](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/about-collaborative-development-models).
|
||||
|
|
18
Cargo.lock
generated
18
Cargo.lock
generated
|
@ -63,7 +63,7 @@ name = "backtrace"
|
|||
version = "0.3.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"backtrace-sys 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"backtrace-sys 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -71,7 +71,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "backtrace-sys"
|
||||
version = "0.1.36"
|
||||
version = "0.1.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cc 1.0.52 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -422,7 +422,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
dependencies = [
|
||||
"proc-macro-hack 0.5.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
|
@ -938,7 +938,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.3"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1063,7 +1063,7 @@ version = "1.0.106"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
|
@ -1107,7 +1107,7 @@ version = "1.0.18"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
|
@ -1191,7 +1191,7 @@ version = "0.5.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
|
@ -1310,7 +1310,7 @@ dependencies = [
|
|||
"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
|
||||
"checksum backtrace 0.3.46 (registry+https://github.com/rust-lang/crates.io-index)" = "b1e692897359247cc6bb902933361652380af0f1b7651ae5c5013407f30e109e"
|
||||
"checksum backtrace-sys 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)" = "78848718ee1255a2485d1309ad9cdecfc2e7d0362dd11c6829364c6b35ae1bc7"
|
||||
"checksum backtrace-sys 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "18fbebbe1c9d1f383a9cc7e8ccdb471b91c8d024ee9c2ca5b5346121fe8b4399"
|
||||
"checksum base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
|
||||
"checksum battery 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)" = "36a698e449024a5d18994a815998bf5e2e4bc1883e35a7d7ba95b6b69ee45907"
|
||||
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
|
@ -1403,7 +1403,7 @@ dependencies = [
|
|||
"checksum proc-macro-hack 0.5.15 (registry+https://github.com/rust-lang/crates.io-index)" = "0d659fe7c6d27f25e9d80a1a094c223f5246f6a6596453e09d7229bf42750b63"
|
||||
"checksum proc-macro-nested 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8e946095f9d3ed29ec38de908c22f95d9ac008e424c7bcae54c75a79c527c694"
|
||||
"checksum proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)" = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3"
|
||||
"checksum quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f"
|
||||
"checksum quote 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4c1f4b0efa5fc5e8ceb705136bfee52cfdb6a4e3509f770b478cd6ed434232a7"
|
||||
"checksum raw-cpuid 7.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b4a349ca83373cfa5d6dbb66fd76e58b2cca08da71a5f6400de0a0a6a9bceeaf"
|
||||
"checksum rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "db6ce3297f9c85e16621bb8cca38a06779ffc31bb8184e1be4bed2be4678a098"
|
||||
"checksum rayon-core 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "08a89b46efaf957e52b18062fb2f4660f8b8a4dde1807ca002690868ef2c85a9"
|
||||
|
|
|
@ -27,11 +27,9 @@ crossterm = "0.17"
|
|||
chrono = "0.4.11"
|
||||
clap = "2.33.0"
|
||||
dirs = "2.0.2"
|
||||
fern = "0.6.0"
|
||||
futures = "0.3.4"
|
||||
heim = "0.0.10"
|
||||
itertools = "0.9.0"
|
||||
log = "0.4.8"
|
||||
regex = "1.3"
|
||||
sysinfo = "0.14"
|
||||
toml = "0.5.6"
|
||||
|
@ -42,9 +40,14 @@ serde = {version = "1.0", features = ["derive"] }
|
|||
unicode-segmentation = "1.6.0"
|
||||
unicode-width = "0.1.7"
|
||||
|
||||
# For debugging only...
|
||||
fern = "0.6.0"
|
||||
log = "0.4.8"
|
||||
|
||||
tui = {version = "0.9", features = ["crossterm"], default-features = false }
|
||||
# tui = {git = "https://github.com/ClementTsang/tui-rs", features = ["crossterm"], default-features = false }
|
||||
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi = "0.3.8"
|
||||
|
||||
|
|
116
src/app.rs
116
src/app.rs
|
@ -19,6 +19,7 @@ pub mod data_farmer;
|
|||
pub mod data_harvester;
|
||||
pub mod layout_manager;
|
||||
mod process_killer;
|
||||
pub mod query;
|
||||
pub mod states;
|
||||
|
||||
const MAX_SEARCH_LENGTH: usize = 200;
|
||||
|
@ -307,19 +308,6 @@ impl App {
|
|||
let is_in_search_widget = self.is_in_search_widget();
|
||||
if !self.is_in_dialog() {
|
||||
if is_in_search_widget {
|
||||
if let Some(proc_widget_state) = self
|
||||
.proc_state
|
||||
.widget_states
|
||||
.get_mut(&(self.current_widget.widget_id - 1))
|
||||
{
|
||||
if !proc_widget_state.is_grouped {
|
||||
if proc_widget_state.process_search_state.is_searching_with_pid {
|
||||
self.search_with_name();
|
||||
} else {
|
||||
self.search_with_pid();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let Some(proc_widget_state) = self
|
||||
.proc_state
|
||||
.widget_states
|
||||
|
@ -327,11 +315,7 @@ impl App {
|
|||
{
|
||||
// Toggles process widget grouping state
|
||||
proc_widget_state.is_grouped = !(proc_widget_state.is_grouped);
|
||||
if proc_widget_state.is_grouped {
|
||||
self.search_with_name();
|
||||
} else {
|
||||
self.proc_state.force_update = Some(self.current_widget.widget_id);
|
||||
}
|
||||
self.proc_state.force_update = Some(self.current_widget.widget_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -387,9 +371,6 @@ impl App {
|
|||
.process_search_state
|
||||
.search_state
|
||||
.is_enabled = true;
|
||||
if proc_widget_state.is_grouped {
|
||||
self.search_with_name();
|
||||
}
|
||||
self.move_widget_selection_down();
|
||||
}
|
||||
}
|
||||
|
@ -426,44 +407,6 @@ impl App {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn search_with_pid(&mut self) {
|
||||
if !self.is_in_dialog() {
|
||||
if let Some(proc_widget_state) = self
|
||||
.proc_state
|
||||
.widget_states
|
||||
.get_mut(&(self.current_widget.widget_id - 1))
|
||||
{
|
||||
if proc_widget_state
|
||||
.process_search_state
|
||||
.search_state
|
||||
.is_enabled
|
||||
{
|
||||
proc_widget_state.process_search_state.is_searching_with_pid = true;
|
||||
self.proc_state.force_update = Some(self.current_widget.widget_id - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn search_with_name(&mut self) {
|
||||
if !self.is_in_dialog() {
|
||||
if let Some(proc_widget_state) = self
|
||||
.proc_state
|
||||
.widget_states
|
||||
.get_mut(&(self.current_widget.widget_id - 1))
|
||||
{
|
||||
if proc_widget_state
|
||||
.process_search_state
|
||||
.search_state
|
||||
.is_enabled
|
||||
{
|
||||
proc_widget_state.process_search_state.is_searching_with_pid = false;
|
||||
self.proc_state.force_update = Some(self.current_widget.widget_id - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_ignore_case(&mut self) {
|
||||
let is_in_search_widget = self.is_in_search_widget();
|
||||
if let Some(proc_widget_state) = self
|
||||
|
@ -475,7 +418,7 @@ impl App {
|
|||
proc_widget_state
|
||||
.process_search_state
|
||||
.search_toggle_ignore_case();
|
||||
proc_widget_state.update_regex();
|
||||
proc_widget_state.update_query();
|
||||
self.proc_state.force_update = Some(self.current_widget.widget_id - 1);
|
||||
}
|
||||
}
|
||||
|
@ -492,7 +435,7 @@ impl App {
|
|||
proc_widget_state
|
||||
.process_search_state
|
||||
.search_toggle_whole_word();
|
||||
proc_widget_state.update_regex();
|
||||
proc_widget_state.update_query();
|
||||
self.proc_state.force_update = Some(self.current_widget.widget_id - 1);
|
||||
}
|
||||
}
|
||||
|
@ -507,7 +450,7 @@ impl App {
|
|||
{
|
||||
if is_in_search_widget && proc_widget_state.is_search_enabled() {
|
||||
proc_widget_state.process_search_state.search_toggle_regex();
|
||||
proc_widget_state.update_regex();
|
||||
proc_widget_state.update_query();
|
||||
self.proc_state.force_update = Some(self.current_widget.widget_id - 1);
|
||||
}
|
||||
}
|
||||
|
@ -575,7 +518,7 @@ impl App {
|
|||
true,
|
||||
);
|
||||
|
||||
proc_widget_state.update_regex();
|
||||
proc_widget_state.update_query();
|
||||
self.proc_state.force_update = Some(self.current_widget.widget_id - 1);
|
||||
}
|
||||
} else {
|
||||
|
@ -630,24 +573,18 @@ impl App {
|
|||
.search_state
|
||||
.cursor_direction = CursorDirection::LEFT;
|
||||
|
||||
proc_widget_state.update_regex();
|
||||
proc_widget_state.update_query();
|
||||
self.proc_state.force_update = Some(self.current_widget.widget_id - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_current_regex_matcher(
|
||||
&self, widget_id: u64,
|
||||
) -> &Option<std::result::Result<regex::Regex, regex::Error>> {
|
||||
match self.proc_state.widget_states.get(&widget_id) {
|
||||
Some(proc_widget_state) => {
|
||||
&proc_widget_state
|
||||
.process_search_state
|
||||
.search_state
|
||||
.current_regex
|
||||
}
|
||||
None => &None,
|
||||
pub fn get_process_filter(&self, widget_id: u64) -> &Option<query::Query> {
|
||||
if let Some(process_widget_state) = self.proc_state.widget_states.get(&widget_id) {
|
||||
&process_widget_state.process_search_state.search_state.query
|
||||
} else {
|
||||
&None
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -872,6 +809,8 @@ impl App {
|
|||
}
|
||||
|
||||
pub fn start_dd(&mut self) {
|
||||
self.reset_multi_tap_keys();
|
||||
|
||||
if let Some(proc_widget_state) = self
|
||||
.proc_state
|
||||
.widget_states
|
||||
|
@ -885,32 +824,25 @@ impl App {
|
|||
if proc_widget_state.scroll_state.current_scroll_position
|
||||
< corresponding_filtered_process_list.len() as u64
|
||||
{
|
||||
let current_process = if self.is_grouped(self.current_widget.widget_id) {
|
||||
let group_pids = &corresponding_filtered_process_list
|
||||
[proc_widget_state.scroll_state.current_scroll_position as usize]
|
||||
.group_pids;
|
||||
|
||||
let mut ret = ("".to_string(), group_pids.clone());
|
||||
|
||||
for pid in group_pids {
|
||||
if let Some(process) = self.canvas_data.process_data.get(&pid) {
|
||||
ret.0 = process.name.clone();
|
||||
break;
|
||||
}
|
||||
let current_process: (String, Vec<u32>);
|
||||
if self.is_grouped(self.current_widget.widget_id) {
|
||||
if let Some(process) = &corresponding_filtered_process_list
|
||||
.get(proc_widget_state.scroll_state.current_scroll_position as usize)
|
||||
{
|
||||
current_process = (process.name.to_string(), process.group_pids.clone())
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
ret
|
||||
} else {
|
||||
let process = corresponding_filtered_process_list
|
||||
[proc_widget_state.scroll_state.current_scroll_position as usize]
|
||||
.clone();
|
||||
(process.name.clone(), vec![process.pid])
|
||||
current_process = (process.name.clone(), vec![process.pid])
|
||||
};
|
||||
|
||||
self.to_delete_process_list = Some(current_process);
|
||||
self.delete_dialog_state.is_showing_dd = true;
|
||||
}
|
||||
|
||||
self.reset_multi_tap_keys();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -977,7 +909,7 @@ impl App {
|
|||
.char_cursor_position +=
|
||||
UnicodeWidthChar::width(caught_char).unwrap_or(0);
|
||||
|
||||
proc_widget_state.update_regex();
|
||||
proc_widget_state.update_query();
|
||||
self.proc_state.force_update = Some(self.current_widget.widget_id - 1);
|
||||
proc_widget_state
|
||||
.process_search_state
|
||||
|
|
267
src/app/query.rs
Normal file
267
src/app/query.rs
Normal file
|
@ -0,0 +1,267 @@
|
|||
use crate::{
|
||||
data_conversion::ConvertedProcessData,
|
||||
utils::error::{BottomError, Result},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Query {
|
||||
pub query: And,
|
||||
}
|
||||
|
||||
impl Query {
|
||||
pub fn process_regexes(
|
||||
&mut self, is_searching_whole_word: bool, is_ignoring_case: bool,
|
||||
is_searching_with_regex: bool,
|
||||
) -> Result<()> {
|
||||
self.query.process_regexes(
|
||||
is_searching_whole_word,
|
||||
is_ignoring_case,
|
||||
is_searching_with_regex,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn check(&self, process: &ConvertedProcessData) -> bool {
|
||||
self.query.check(process)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct And {
|
||||
pub lhs: Or,
|
||||
pub rhs: Option<Box<Or>>,
|
||||
}
|
||||
|
||||
impl And {
|
||||
pub fn process_regexes(
|
||||
&mut self, is_searching_whole_word: bool, is_ignoring_case: bool,
|
||||
is_searching_with_regex: bool,
|
||||
) -> Result<()> {
|
||||
self.lhs.process_regexes(
|
||||
is_searching_whole_word,
|
||||
is_ignoring_case,
|
||||
is_searching_with_regex,
|
||||
)?;
|
||||
if let Some(rhs) = &mut self.rhs {
|
||||
rhs.process_regexes(
|
||||
is_searching_whole_word,
|
||||
is_ignoring_case,
|
||||
is_searching_with_regex,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn check(&self, process: &ConvertedProcessData) -> bool {
|
||||
if let Some(rhs) = &self.rhs {
|
||||
self.lhs.check(process) && rhs.check(process)
|
||||
} else {
|
||||
self.lhs.check(process)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Or {
|
||||
pub lhs: Prefix,
|
||||
pub rhs: Option<Box<Prefix>>,
|
||||
}
|
||||
|
||||
impl Or {
|
||||
pub fn process_regexes(
|
||||
&mut self, is_searching_whole_word: bool, is_ignoring_case: bool,
|
||||
is_searching_with_regex: bool,
|
||||
) -> Result<()> {
|
||||
self.lhs.process_regexes(
|
||||
is_searching_whole_word,
|
||||
is_ignoring_case,
|
||||
is_searching_with_regex,
|
||||
)?;
|
||||
if let Some(rhs) = &mut self.rhs {
|
||||
rhs.process_regexes(
|
||||
is_searching_whole_word,
|
||||
is_ignoring_case,
|
||||
is_searching_with_regex,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn check(&self, process: &ConvertedProcessData) -> bool {
|
||||
if let Some(rhs) = &self.rhs {
|
||||
self.lhs.check(process) || rhs.check(process)
|
||||
} else {
|
||||
self.lhs.check(process)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PrefixType {
|
||||
Pid,
|
||||
Cpu,
|
||||
Mem,
|
||||
Rps,
|
||||
Wps,
|
||||
TRead,
|
||||
TWrite,
|
||||
Name,
|
||||
__Nonexhaustive,
|
||||
}
|
||||
|
||||
impl std::str::FromStr for PrefixType {
|
||||
type Err = BottomError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
use PrefixType::*;
|
||||
|
||||
let lower_case = s.to_lowercase();
|
||||
match lower_case.as_str() {
|
||||
"cpu" => Ok(Cpu),
|
||||
"mem" => Ok(Mem),
|
||||
"r" => Ok(Rps),
|
||||
"w" => Ok(Wps),
|
||||
"read" => Ok(TRead),
|
||||
"write" => Ok(TWrite),
|
||||
"pid" => Ok(Pid),
|
||||
_ => Ok(Name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Prefix {
|
||||
pub and: Option<Box<And>>,
|
||||
pub regex_prefix: Option<(PrefixType, StringQuery)>,
|
||||
pub compare_prefix: Option<(PrefixType, NumericalQuery)>,
|
||||
}
|
||||
|
||||
impl Prefix {
|
||||
pub fn process_regexes(
|
||||
&mut self, is_searching_whole_word: bool, is_ignoring_case: bool,
|
||||
is_searching_with_regex: bool,
|
||||
) -> Result<()> {
|
||||
if let Some(and) = &mut self.and {
|
||||
return and.process_regexes(
|
||||
is_searching_whole_word,
|
||||
is_ignoring_case,
|
||||
is_searching_with_regex,
|
||||
);
|
||||
} else if let Some((prefix_type, query_content)) = &mut self.regex_prefix {
|
||||
if let StringQuery::Value(regex_string) = query_content {
|
||||
match prefix_type {
|
||||
PrefixType::Pid | PrefixType::Name => {
|
||||
let escaped_regex: String;
|
||||
let final_regex_string = &format!(
|
||||
"{}{}{}{}",
|
||||
if is_searching_whole_word { "^" } else { "" },
|
||||
if is_ignoring_case { "(?i)" } else { "" },
|
||||
if !is_searching_with_regex {
|
||||
escaped_regex = regex::escape(regex_string);
|
||||
&escaped_regex
|
||||
} else {
|
||||
regex_string
|
||||
},
|
||||
if is_searching_whole_word { "$" } else { "" },
|
||||
);
|
||||
|
||||
let taken_pwc = self.regex_prefix.take();
|
||||
if let Some((taken_pt, _)) = taken_pwc {
|
||||
self.regex_prefix = Some((
|
||||
taken_pt,
|
||||
StringQuery::Regex(regex::Regex::new(final_regex_string)?),
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn check(&self, process: &ConvertedProcessData) -> bool {
|
||||
fn matches_condition(condition: &QueryComparison, lhs: f64, rhs: f64) -> bool {
|
||||
match condition {
|
||||
QueryComparison::Equal => lhs == rhs,
|
||||
QueryComparison::Less => lhs < rhs,
|
||||
QueryComparison::Greater => lhs > rhs,
|
||||
QueryComparison::LessOrEqual => lhs <= rhs,
|
||||
QueryComparison::GreaterOrEqual => lhs >= rhs,
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(and) = &self.and {
|
||||
and.check(process)
|
||||
} else if let Some((prefix_type, query_content)) = &self.regex_prefix {
|
||||
if let StringQuery::Regex(r) = query_content {
|
||||
match prefix_type {
|
||||
PrefixType::Name => r.is_match(process.name.as_str()),
|
||||
PrefixType::Pid => r.is_match(process.pid.to_string().as_str()),
|
||||
_ => true,
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
} else if let Some((prefix_type, numerical_query)) = &self.compare_prefix {
|
||||
match prefix_type {
|
||||
PrefixType::Cpu => matches_condition(
|
||||
&numerical_query.condition,
|
||||
process.cpu_usage,
|
||||
numerical_query.value,
|
||||
),
|
||||
PrefixType::Mem => matches_condition(
|
||||
&numerical_query.condition,
|
||||
process.mem_usage,
|
||||
numerical_query.value,
|
||||
),
|
||||
PrefixType::Rps => matches_condition(
|
||||
&numerical_query.condition,
|
||||
process.rps_f64,
|
||||
numerical_query.value,
|
||||
),
|
||||
PrefixType::Wps => matches_condition(
|
||||
&numerical_query.condition,
|
||||
process.wps_f64,
|
||||
numerical_query.value,
|
||||
),
|
||||
PrefixType::TRead => matches_condition(
|
||||
&numerical_query.condition,
|
||||
process.tr_f64,
|
||||
numerical_query.value,
|
||||
),
|
||||
PrefixType::TWrite => matches_condition(
|
||||
&numerical_query.condition,
|
||||
process.tw_f64,
|
||||
numerical_query.value,
|
||||
),
|
||||
_ => true,
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum QueryComparison {
|
||||
Equal,
|
||||
Less,
|
||||
Greater,
|
||||
LessOrEqual,
|
||||
GreaterOrEqual,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum StringQuery {
|
||||
Value(String),
|
||||
Regex(regex::Regex),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NumericalQuery {
|
||||
pub condition: QueryComparison,
|
||||
pub value: f64,
|
||||
}
|
|
@ -1,10 +1,18 @@
|
|||
use std::{collections::HashMap, time::Instant};
|
||||
use std::{
|
||||
collections::{HashMap, VecDeque},
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use unicode_segmentation::GraphemeCursor;
|
||||
|
||||
use tui::widgets::TableState;
|
||||
|
||||
use crate::{app::layout_manager::BottomWidgetType, constants, data_harvester::processes};
|
||||
use crate::{
|
||||
app::{layout_manager::BottomWidgetType, query::*},
|
||||
constants,
|
||||
data_harvester::processes,
|
||||
utils::error::{BottomError::*, Result},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ScrollDirection {
|
||||
|
@ -61,7 +69,6 @@ impl Default for AppHelpDialogState {
|
|||
pub struct AppSearchState {
|
||||
pub is_enabled: bool,
|
||||
pub current_search_query: String,
|
||||
pub current_regex: Option<std::result::Result<regex::Regex, regex::Error>>,
|
||||
pub is_blank_search: bool,
|
||||
pub is_invalid_search: bool,
|
||||
pub grapheme_cursor: GraphemeCursor,
|
||||
|
@ -69,6 +76,8 @@ pub struct AppSearchState {
|
|||
pub cursor_bar: usize,
|
||||
/// This represents the position in terms of CHARACTERS, not graphemes
|
||||
pub char_cursor_position: usize,
|
||||
/// The query
|
||||
pub query: Option<Query>,
|
||||
}
|
||||
|
||||
impl Default for AppSearchState {
|
||||
|
@ -76,13 +85,13 @@ impl Default for AppSearchState {
|
|||
AppSearchState {
|
||||
is_enabled: false,
|
||||
current_search_query: String::default(),
|
||||
current_regex: None,
|
||||
is_invalid_search: false,
|
||||
is_blank_search: true,
|
||||
grapheme_cursor: GraphemeCursor::new(0, 0, true),
|
||||
cursor_direction: CursorDirection::RIGHT,
|
||||
cursor_bar: 0,
|
||||
char_cursor_position: 0,
|
||||
query: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -104,7 +113,6 @@ impl AppSearchState {
|
|||
/// ProcessSearchState only deals with process' search's current settings and state.
|
||||
pub struct ProcessSearchState {
|
||||
pub search_state: AppSearchState,
|
||||
pub is_searching_with_pid: bool,
|
||||
pub is_ignoring_case: bool,
|
||||
pub is_searching_whole_word: bool,
|
||||
pub is_searching_with_regex: bool,
|
||||
|
@ -114,7 +122,6 @@ impl Default for ProcessSearchState {
|
|||
fn default() -> Self {
|
||||
ProcessSearchState {
|
||||
search_state: AppSearchState::default(),
|
||||
is_searching_with_pid: false,
|
||||
is_ignoring_case: true,
|
||||
is_searching_whole_word: false,
|
||||
is_searching_with_regex: false,
|
||||
|
@ -188,7 +195,7 @@ impl ProcWidgetState {
|
|||
&self.process_search_state.search_state.current_search_query
|
||||
}
|
||||
|
||||
pub fn update_regex(&mut self) {
|
||||
pub fn update_query(&mut self) {
|
||||
if self
|
||||
.process_search_state
|
||||
.search_state
|
||||
|
@ -197,39 +204,13 @@ impl ProcWidgetState {
|
|||
{
|
||||
self.process_search_state.search_state.is_invalid_search = false;
|
||||
self.process_search_state.search_state.is_blank_search = true;
|
||||
} else {
|
||||
let regex_string = &self.process_search_state.search_state.current_search_query;
|
||||
let escaped_regex: String;
|
||||
let final_regex_string = &format!(
|
||||
"{}{}{}{}",
|
||||
if self.process_search_state.is_searching_whole_word {
|
||||
"^"
|
||||
} else {
|
||||
""
|
||||
},
|
||||
if self.process_search_state.is_ignoring_case {
|
||||
"(?i)"
|
||||
} else {
|
||||
""
|
||||
},
|
||||
if !self.process_search_state.is_searching_with_regex {
|
||||
escaped_regex = regex::escape(regex_string);
|
||||
&escaped_regex
|
||||
} else {
|
||||
regex_string
|
||||
},
|
||||
if self.process_search_state.is_searching_whole_word {
|
||||
"$"
|
||||
} else {
|
||||
""
|
||||
},
|
||||
);
|
||||
|
||||
let new_regex = regex::Regex::new(final_regex_string);
|
||||
} else if let Ok(parsed_query) = self.parse_query() {
|
||||
self.process_search_state.search_state.query = Some(parsed_query);
|
||||
self.process_search_state.search_state.is_blank_search = false;
|
||||
self.process_search_state.search_state.is_invalid_search = new_regex.is_err();
|
||||
|
||||
self.process_search_state.search_state.current_regex = Some(new_regex);
|
||||
self.process_search_state.search_state.is_invalid_search = false;
|
||||
} else {
|
||||
self.process_search_state.search_state.is_blank_search = false;
|
||||
self.process_search_state.search_state.is_invalid_search = true;
|
||||
}
|
||||
self.scroll_state.previous_scroll_position = 0;
|
||||
self.scroll_state.current_scroll_position = 0;
|
||||
|
@ -260,6 +241,257 @@ impl ProcWidgetState {
|
|||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// The filtering function. Based on the results of the query.
|
||||
pub fn matches_filter(&self) -> bool {
|
||||
// The way this will have to work is that given a "query" structure, we have
|
||||
// to filter based on it.
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// In charge of parsing the given query.
|
||||
/// We are defining the following language for a query (case-insensitive prefixes):
|
||||
///
|
||||
/// - Process names: No prefix required, can use regex, match word, or case.
|
||||
/// Enclosing anything, including prefixes, in quotes, means we treat it as an entire process
|
||||
/// rather than a prefix.
|
||||
/// - PIDs: Use prefix `pid`, can use regex or match word (case is irrelevant).
|
||||
/// - CPU: Use prefix `cpu`, cannot use r/m/c (regex, match word, case). Can compare.
|
||||
/// - MEM: Use prefix `mem`, cannot use r/m/c. Can compare.
|
||||
/// - STATE: Use prefix `state`, TODO when we update how state looks in 0.5 probably.
|
||||
/// - Read/s: Use prefix `r`. Can compare.
|
||||
/// - Write/s: Use prefix `w`. Can compare.
|
||||
/// - Total read: Use prefix `read`. Can compare.
|
||||
/// - Total write: Use prefix `write`. Can compare.
|
||||
///
|
||||
/// For queries, whitespaces are our delimiters. We will merge together any adjacent non-prefixed
|
||||
/// or quoted elements after splitting to treat as process names.
|
||||
/// Furthermore, we want to support boolean joiners like AND and OR, and brackets.
|
||||
fn parse_query(&self) -> Result<Query> {
|
||||
fn process_string_to_filter(query: &mut VecDeque<String>) -> Result<Query> {
|
||||
Ok(Query {
|
||||
query: process_and(query)?,
|
||||
})
|
||||
}
|
||||
|
||||
fn process_and(query: &mut VecDeque<String>) -> Result<And> {
|
||||
let mut lhs = process_or(query)?;
|
||||
let mut rhs: Option<Box<Or>> = None;
|
||||
|
||||
while let Some(queue_top) = query.front() {
|
||||
if queue_top.to_lowercase() == "and" {
|
||||
query.pop_front();
|
||||
rhs = Some(Box::new(process_or(query)?));
|
||||
|
||||
if let Some(queue_next) = query.front() {
|
||||
if queue_next.to_lowercase() == "and" {
|
||||
// Must merge LHS and RHS
|
||||
lhs = Or {
|
||||
lhs: Prefix {
|
||||
and: Some(Box::new(And { lhs, rhs })),
|
||||
regex_prefix: None,
|
||||
compare_prefix: None,
|
||||
},
|
||||
rhs: None,
|
||||
};
|
||||
rhs = None;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(And { lhs, rhs })
|
||||
}
|
||||
|
||||
fn process_or(query: &mut VecDeque<String>) -> Result<Or> {
|
||||
let mut lhs = process_prefix(query)?;
|
||||
let mut rhs: Option<Box<Prefix>> = None;
|
||||
|
||||
while let Some(queue_top) = query.front() {
|
||||
if queue_top.to_lowercase() == "or" {
|
||||
query.pop_front();
|
||||
rhs = Some(Box::new(process_prefix(query)?));
|
||||
|
||||
if let Some(queue_next) = query.front() {
|
||||
if queue_next.to_lowercase() == "or" {
|
||||
// Must merge LHS and RHS
|
||||
lhs = Prefix {
|
||||
and: Some(Box::new(And {
|
||||
lhs: Or { lhs, rhs },
|
||||
rhs: None,
|
||||
})),
|
||||
regex_prefix: None,
|
||||
compare_prefix: None,
|
||||
};
|
||||
rhs = None;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Or { lhs, rhs })
|
||||
}
|
||||
|
||||
fn process_prefix(query: &mut VecDeque<String>) -> Result<Prefix> {
|
||||
if let Some(queue_top) = query.pop_front() {
|
||||
if queue_top == "(" {
|
||||
// Get content within bracket; and check if paren is complete
|
||||
let and = process_and(query)?;
|
||||
if let Some(close_paren) = query.pop_front() {
|
||||
if close_paren.to_lowercase() == ")" {
|
||||
return Ok(Prefix {
|
||||
and: Some(Box::new(and)),
|
||||
regex_prefix: None,
|
||||
compare_prefix: None,
|
||||
});
|
||||
} else {
|
||||
return Err(QueryError("Missing closing parentheses".into()));
|
||||
}
|
||||
} else {
|
||||
return Err(QueryError("Missing closing parentheses".into()));
|
||||
}
|
||||
} else if queue_top == ")" {
|
||||
// This is actually caught by the regex creation, but it seems a bit
|
||||
// sloppy to leave that up to that to do so...
|
||||
|
||||
return Err(QueryError("Missing opening parentheses".into()));
|
||||
} else {
|
||||
// Get prefix type...
|
||||
let prefix_type = queue_top.parse::<PrefixType>()?;
|
||||
let content = if let PrefixType::Name = prefix_type {
|
||||
Some(queue_top)
|
||||
} else {
|
||||
query.pop_front()
|
||||
};
|
||||
|
||||
if let Some(content) = content {
|
||||
match &prefix_type {
|
||||
PrefixType::Name => {
|
||||
return Ok(Prefix {
|
||||
and: None,
|
||||
regex_prefix: Some((prefix_type, StringQuery::Value(content))),
|
||||
compare_prefix: None,
|
||||
})
|
||||
}
|
||||
PrefixType::Pid => {
|
||||
// We have to check if someone put an "="...
|
||||
if content == "=" {
|
||||
// Check next string if possible
|
||||
if let Some(queue_next) = query.pop_front() {
|
||||
return Ok(Prefix {
|
||||
and: None,
|
||||
regex_prefix: Some((
|
||||
prefix_type,
|
||||
StringQuery::Value(queue_next),
|
||||
)),
|
||||
compare_prefix: None,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return Ok(Prefix {
|
||||
and: None,
|
||||
regex_prefix: Some((
|
||||
prefix_type,
|
||||
StringQuery::Value(content),
|
||||
)),
|
||||
compare_prefix: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// Now we gotta parse the content... yay.
|
||||
|
||||
let mut condition: Option<QueryComparison> = None;
|
||||
let mut value: Option<f64> = None;
|
||||
|
||||
if content == "=" {
|
||||
// TODO: Do we want to allow just an empty space to work here too? ie: cpu 5?
|
||||
condition = Some(QueryComparison::Equal);
|
||||
if let Some(queue_next) = query.pop_front() {
|
||||
value = queue_next.parse::<f64>().ok();
|
||||
}
|
||||
} else if content == ">" || content == "<" {
|
||||
// We also have to check if the next string is an "="...
|
||||
if let Some(queue_next) = query.pop_front() {
|
||||
if queue_next == "=" {
|
||||
condition = Some(if content == ">" {
|
||||
QueryComparison::GreaterOrEqual
|
||||
} else {
|
||||
QueryComparison::LessOrEqual
|
||||
});
|
||||
if let Some(queue_next_next) = query.pop_front() {
|
||||
value = queue_next_next.parse::<f64>().ok();
|
||||
}
|
||||
} else {
|
||||
condition = Some(if content == ">" {
|
||||
QueryComparison::Greater
|
||||
} else {
|
||||
QueryComparison::Less
|
||||
});
|
||||
value = queue_next.parse::<f64>().ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(condition) = condition {
|
||||
if let Some(value) = value {
|
||||
return Ok(Prefix {
|
||||
and: None,
|
||||
regex_prefix: None,
|
||||
compare_prefix: Some((
|
||||
prefix_type,
|
||||
NumericalQuery { condition, value },
|
||||
)),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(QueryError("Failed to parse comparator.".into()))
|
||||
}
|
||||
|
||||
let mut split_query = VecDeque::new();
|
||||
|
||||
self.get_current_search_query()
|
||||
.split_whitespace()
|
||||
.for_each(|s| {
|
||||
// From https://stackoverflow.com/a/56923739 in order to get a split but include the parentheses
|
||||
let mut last = 0;
|
||||
for (index, matched) in s.match_indices(|x| ['=', '>', '<', '(', ')'].contains(&x))
|
||||
{
|
||||
if last != index {
|
||||
split_query.push_back(s[last..index].to_owned());
|
||||
}
|
||||
split_query.push_back(matched.to_owned());
|
||||
last = index + matched.len();
|
||||
}
|
||||
if last < s.len() {
|
||||
split_query.push_back(s[last..].to_owned());
|
||||
}
|
||||
});
|
||||
|
||||
let mut process_filter = process_string_to_filter(&mut split_query)?;
|
||||
process_filter.process_regexes(
|
||||
self.process_search_state.is_searching_whole_word,
|
||||
self.process_search_state.is_ignoring_case,
|
||||
self.process_search_state.is_searching_with_regex,
|
||||
)?;
|
||||
|
||||
Ok(process_filter)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ProcState {
|
||||
|
|
|
@ -16,7 +16,6 @@ use widgets::*;
|
|||
use crate::{
|
||||
app::{
|
||||
self,
|
||||
data_harvester::processes::ProcessHarvest,
|
||||
layout_manager::{BottomColRow, BottomLayout, BottomWidgetType},
|
||||
App,
|
||||
},
|
||||
|
@ -41,7 +40,7 @@ pub struct DisplayableData {
|
|||
pub disk_data: Vec<Vec<String>>,
|
||||
pub temp_sensor_data: Vec<Vec<String>>,
|
||||
// Not the final value
|
||||
pub process_data: HashMap<u32, ProcessHarvest>,
|
||||
pub process_data: Vec<ConvertedProcessData>,
|
||||
// Not the final value
|
||||
pub grouped_process_data: Vec<ConvertedProcessData>,
|
||||
// What's actually displayed
|
||||
|
|
|
@ -160,8 +160,7 @@ impl CpuGraphWidget for Painter {
|
|||
self.colours.cpu_colour_styles
|
||||
[itx % self.colours.cpu_colour_styles.len()]
|
||||
})
|
||||
.data(&cpu.cpu_data[..])
|
||||
// .graph_type(tui::widgets::GraphType::Line),
|
||||
.data(&cpu.cpu_data[..]), // .graph_type(tui::widgets::GraphType::Line),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::cmp::max;
|
||||
|
||||
use crate::{
|
||||
app::{self, App, ProcWidgetState},
|
||||
app::{self, App},
|
||||
canvas::{
|
||||
drawing_utils::{
|
||||
get_search_start_position, get_start_position, get_variable_intrinsic_widths,
|
||||
|
@ -44,7 +44,7 @@ impl ProcessTableWidget for Painter {
|
|||
widget_id: u64,
|
||||
) {
|
||||
if let Some(process_widget_state) = app_state.proc_state.widget_states.get(&widget_id) {
|
||||
let search_height = if draw_border { 4 } else { 3 };
|
||||
let search_height = if draw_border { 3 } else { 2 };
|
||||
if process_widget_state.is_search_enabled() {
|
||||
let processes_chunk = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
|
@ -148,7 +148,7 @@ impl ProcessTableWidget for Painter {
|
|||
let wps = "W/s".to_string();
|
||||
let total_read = "Read".to_string();
|
||||
let total_write = "Write".to_string();
|
||||
let process_state = "State".to_string();
|
||||
// let process_state = "State".to_string();
|
||||
|
||||
let direction_val = if proc_widget_state.process_sorting_reverse {
|
||||
"▼".to_string()
|
||||
|
@ -172,7 +172,7 @@ impl ProcessTableWidget for Painter {
|
|||
wps,
|
||||
total_read,
|
||||
total_write,
|
||||
process_state,
|
||||
// process_state,
|
||||
];
|
||||
let process_headers_lens: Vec<usize> = process_headers
|
||||
.iter()
|
||||
|
@ -181,7 +181,7 @@ impl ProcessTableWidget for Painter {
|
|||
|
||||
// Calculate widths
|
||||
let width = f64::from(draw_loc.width);
|
||||
let width_ratios = [0.1, 0.2, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1];
|
||||
let width_ratios = [0.1, 0.2, 0.1, 0.1, 0.1, 0.1, 0.15, 0.15];
|
||||
let variable_intrinsic_results = get_variable_intrinsic_widths(
|
||||
width as u16,
|
||||
&width_ratios,
|
||||
|
@ -278,20 +278,6 @@ impl ProcessTableWidget for Painter {
|
|||
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool,
|
||||
widget_id: u64,
|
||||
) {
|
||||
fn get_prompt_text<'a>(proc_widget_state: &ProcWidgetState) -> &'a str {
|
||||
let pid_search_text = "Search by PID (Tab for Name): ";
|
||||
let name_search_text = "Search by Name (Tab for PID): ";
|
||||
let grouped_search_text = "Search by Name: ";
|
||||
|
||||
if proc_widget_state.is_grouped {
|
||||
grouped_search_text
|
||||
} else if proc_widget_state.process_search_state.is_searching_with_pid {
|
||||
pid_search_text
|
||||
} else {
|
||||
name_search_text
|
||||
}
|
||||
}
|
||||
|
||||
fn build_query<'a>(
|
||||
is_on_widget: bool, grapheme_indices: GraphemeIndices<'a>, start_position: usize,
|
||||
cursor_position: usize, query: &str, currently_selected_text_style: tui::style::Style,
|
||||
|
@ -342,24 +328,11 @@ impl ProcessTableWidget for Painter {
|
|||
if let Some(proc_widget_state) =
|
||||
app_state.proc_state.widget_states.get_mut(&(widget_id - 1))
|
||||
{
|
||||
let chosen_text = get_prompt_text(&proc_widget_state);
|
||||
|
||||
let is_on_widget = widget_id == app_state.current_widget.widget_id;
|
||||
let is_on_processes =
|
||||
is_on_widget || (widget_id - 1 == app_state.current_widget.widget_id);
|
||||
let num_columns = draw_loc.width as usize;
|
||||
let small_mode = num_columns < 70;
|
||||
let search_title: &str = if !small_mode {
|
||||
chosen_text
|
||||
} else if chosen_text.is_empty() {
|
||||
""
|
||||
} else if proc_widget_state.process_search_state.is_searching_with_pid
|
||||
&& !proc_widget_state.is_grouped
|
||||
{
|
||||
"p> "
|
||||
} else {
|
||||
"n> "
|
||||
};
|
||||
let search_title = "> ";
|
||||
|
||||
let num_chars_for_text = search_title.len();
|
||||
|
||||
|
@ -421,34 +394,16 @@ impl ProcessTableWidget for Painter {
|
|||
};
|
||||
|
||||
let mut option_text = vec![];
|
||||
let case_text = format!(
|
||||
"{}({})",
|
||||
if small_mode { "Case" } else { "Match Case " },
|
||||
if self.is_mac_os { "F1" } else { "Alt+C" },
|
||||
);
|
||||
|
||||
let whole_text = format!(
|
||||
"{}({})",
|
||||
if small_mode {
|
||||
"Whole"
|
||||
} else {
|
||||
"Match Whole Word "
|
||||
},
|
||||
if self.is_mac_os { "F2" } else { "Alt+W" },
|
||||
);
|
||||
|
||||
let regex_text = format!(
|
||||
"{}({})",
|
||||
if small_mode { "Regex" } else { "Use Regex " },
|
||||
if self.is_mac_os { "F3" } else { "Alt+R" },
|
||||
);
|
||||
let case_text = format!("Case({})", if self.is_mac_os { "F1" } else { "Alt+C" },);
|
||||
let whole_text = format!("Whole({})", if self.is_mac_os { "F2" } else { "Alt+W" },);
|
||||
let regex_text = format!("Regex({})", if self.is_mac_os { "F3" } else { "Alt+R" },);
|
||||
|
||||
let option_row = vec![
|
||||
Text::raw("\n\n"),
|
||||
Text::raw("\n"),
|
||||
Text::styled(&case_text, case_style),
|
||||
Text::raw(if small_mode { " " } else { " " }),
|
||||
Text::raw(" "),
|
||||
Text::styled(&whole_text, whole_word_style),
|
||||
Text::raw(if small_mode { " " } else { " " }),
|
||||
Text::raw(" "),
|
||||
Text::styled(®ex_text, regex_style),
|
||||
];
|
||||
option_text.extend(option_row);
|
||||
|
|
|
@ -4,11 +4,7 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use crate::{
|
||||
app::{
|
||||
data_farmer,
|
||||
data_harvester::{self, processes::ProcessHarvest},
|
||||
App,
|
||||
},
|
||||
app::{data_farmer, data_harvester, App},
|
||||
utils::gen_util::{get_exact_byte_values, get_simple_byte_values},
|
||||
};
|
||||
|
||||
|
@ -45,6 +41,10 @@ pub struct ConvertedProcessData {
|
|||
pub write_per_sec: String,
|
||||
pub total_read: String,
|
||||
pub total_write: String,
|
||||
pub rps_f64: f64,
|
||||
pub wps_f64: f64,
|
||||
pub tr_f64: f64,
|
||||
pub tw_f64: f64,
|
||||
pub process_states: String,
|
||||
}
|
||||
|
||||
|
@ -399,8 +399,8 @@ pub fn convert_network_data_points(
|
|||
|
||||
pub fn convert_process_data(
|
||||
current_data: &data_farmer::DataCollection,
|
||||
) -> (HashMap<u32, ProcessHarvest>, Vec<ConvertedProcessData>) {
|
||||
let mut single_list: HashMap<u32, ProcessHarvest> = HashMap::new();
|
||||
) -> (Vec<ConvertedProcessData>, Vec<ConvertedProcessData>) {
|
||||
let mut single_list = Vec::new();
|
||||
|
||||
// cpu, mem, pids
|
||||
let mut grouped_hashmap: HashMap<String, SingleProcessData> = std::collections::HashMap::new();
|
||||
|
@ -423,7 +423,35 @@ pub fn convert_process_data(
|
|||
(*entry).total_write += process.total_write_bytes;
|
||||
(*entry).process_state.push(process.process_state_char);
|
||||
|
||||
single_list.insert(process.pid, process.clone());
|
||||
let converted_rps = get_exact_byte_values(process.read_bytes_per_sec, false);
|
||||
let converted_wps = get_exact_byte_values(process.write_bytes_per_sec, false);
|
||||
let converted_total_read = get_exact_byte_values(process.total_read_bytes, false);
|
||||
let converted_total_write = get_exact_byte_values(process.total_write_bytes, false);
|
||||
|
||||
let read_per_sec = format!("{:.*}{}/s", 0, converted_rps.0, converted_rps.1);
|
||||
let write_per_sec = format!("{:.*}{}/s", 0, converted_wps.0, converted_wps.1);
|
||||
let total_read = format!("{:.*}{}", 0, converted_total_read.0, converted_total_read.1);
|
||||
let total_write = format!(
|
||||
"{:.*}{}",
|
||||
0, converted_total_write.0, converted_total_write.1
|
||||
);
|
||||
|
||||
single_list.push(ConvertedProcessData {
|
||||
pid: process.pid,
|
||||
name: process.name.to_string(),
|
||||
cpu_usage: process.cpu_usage_percent,
|
||||
mem_usage: process.mem_usage_percent,
|
||||
group_pids: vec![process.pid],
|
||||
read_per_sec,
|
||||
write_per_sec,
|
||||
total_read,
|
||||
total_write,
|
||||
rps_f64: converted_rps.0,
|
||||
wps_f64: converted_wps.0,
|
||||
tr_f64: converted_total_read.0,
|
||||
tw_f64: converted_total_write.0,
|
||||
process_states: process.process_state.to_owned(),
|
||||
});
|
||||
}
|
||||
|
||||
let grouped_list: Vec<ConvertedProcessData> = grouped_hashmap
|
||||
|
@ -453,6 +481,10 @@ pub fn convert_process_data(
|
|||
write_per_sec,
|
||||
total_read,
|
||||
total_write,
|
||||
rps_f64: converted_rps.0,
|
||||
wps_f64: converted_wps.0,
|
||||
tr_f64: converted_total_read.0,
|
||||
tw_f64: converted_total_write.0,
|
||||
process_states: p.process_state,
|
||||
}
|
||||
})
|
||||
|
|
91
src/main.rs
91
src/main.rs
|
@ -98,7 +98,10 @@ fn get_matches() -> clap::ArgMatches<'static> {
|
|||
}
|
||||
|
||||
fn main() -> error::Result<()> {
|
||||
create_logger()?;
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
utils::logging::init_logger()?;
|
||||
}
|
||||
let matches = get_matches();
|
||||
|
||||
let config: Config = create_config(matches.value_of("CONFIG_LOCATION"))?;
|
||||
|
@ -314,15 +317,9 @@ fn handle_key_event_or_break(
|
|||
// Otherwise, track the modifier as well...
|
||||
if let KeyModifiers::ALT = event.modifiers {
|
||||
match event.code {
|
||||
KeyCode::Char('c') | KeyCode::Char('C') => {
|
||||
app.toggle_ignore_case();
|
||||
}
|
||||
KeyCode::Char('w') | KeyCode::Char('W') => {
|
||||
app.toggle_search_whole_word();
|
||||
}
|
||||
KeyCode::Char('r') | KeyCode::Char('R') => {
|
||||
app.toggle_search_regex();
|
||||
}
|
||||
KeyCode::Char('c') | KeyCode::Char('C') => app.toggle_ignore_case(),
|
||||
KeyCode::Char('w') | KeyCode::Char('W') => app.toggle_search_whole_word(),
|
||||
KeyCode::Char('r') | KeyCode::Char('R') => app.toggle_search_regex(),
|
||||
KeyCode::Char('h') => app.on_left_key(),
|
||||
KeyCode::Char('l') => app.on_right_key(),
|
||||
_ => {}
|
||||
|
@ -370,13 +367,6 @@ fn handle_key_event_or_break(
|
|||
false
|
||||
}
|
||||
|
||||
fn create_logger() -> error::Result<()> {
|
||||
if cfg!(debug_assertions) {
|
||||
utils::logging::init_logger()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_config(flag_config_location: Option<&str>) -> error::Result<Config> {
|
||||
use std::{ffi::OsString, fs};
|
||||
let config_path = if let Some(conf_loc) = flag_config_location {
|
||||
|
@ -594,7 +584,6 @@ fn update_all_process_lists(app: &mut App) {
|
|||
}
|
||||
|
||||
fn update_final_process_list(app: &mut App, widget_id: u64) {
|
||||
use utils::gen_util::get_exact_byte_values;
|
||||
let is_invalid_or_blank = match app.proc_state.widget_states.get(&widget_id) {
|
||||
Some(process_state) => process_state
|
||||
.process_search_state
|
||||
|
@ -603,77 +592,43 @@ fn update_final_process_list(app: &mut App, widget_id: u64) {
|
|||
None => false,
|
||||
};
|
||||
|
||||
let process_filter = app.get_process_filter(widget_id);
|
||||
let filtered_process_data: Vec<ConvertedProcessData> = if app.is_grouped(widget_id) {
|
||||
app.canvas_data
|
||||
.grouped_process_data
|
||||
.iter()
|
||||
.filter(|process| {
|
||||
if is_invalid_or_blank {
|
||||
return true;
|
||||
} else if let Some(matcher_result) = app.get_current_regex_matcher(widget_id) {
|
||||
if let Ok(matcher) = matcher_result {
|
||||
return matcher.is_match(&process.name);
|
||||
}
|
||||
true
|
||||
} else if let Some(process_filter) = process_filter {
|
||||
process_filter.check(process)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
|
||||
true
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
} else {
|
||||
let is_searching_with_pid = match app.proc_state.widget_states.get(&widget_id) {
|
||||
Some(process_state) => process_state.process_search_state.is_searching_with_pid,
|
||||
None => false,
|
||||
};
|
||||
|
||||
app.canvas_data
|
||||
.process_data
|
||||
.iter()
|
||||
.filter_map(|(_pid, process)| {
|
||||
.filter_map(|process| {
|
||||
let mut result = true;
|
||||
if !is_invalid_or_blank {
|
||||
if let Some(matcher_result) = app.get_current_regex_matcher(widget_id) {
|
||||
if let Ok(matcher) = matcher_result {
|
||||
if is_searching_with_pid {
|
||||
result = matcher.is_match(&process.pid.to_string());
|
||||
} else {
|
||||
result = matcher.is_match(&process.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
result = if let Some(process_filter) = process_filter {
|
||||
process_filter.check(&process)
|
||||
} else {
|
||||
true
|
||||
};
|
||||
}
|
||||
|
||||
let converted_rps = get_exact_byte_values(process.read_bytes_per_sec, false);
|
||||
let converted_wps = get_exact_byte_values(process.write_bytes_per_sec, false);
|
||||
let converted_total_read = get_exact_byte_values(process.total_read_bytes, false);
|
||||
let converted_total_write = get_exact_byte_values(process.total_write_bytes, false);
|
||||
|
||||
let read_per_sec = format!("{:.*}{}/s", 0, converted_rps.0, converted_rps.1);
|
||||
let write_per_sec = format!("{:.*}{}/s", 0, converted_wps.0, converted_wps.1);
|
||||
let total_read =
|
||||
format!("{:.*}{}", 0, converted_total_read.0, converted_total_read.1);
|
||||
let total_write = format!(
|
||||
"{:.*}{}",
|
||||
0, converted_total_write.0, converted_total_write.1
|
||||
);
|
||||
|
||||
if result {
|
||||
return Some(ConvertedProcessData {
|
||||
pid: process.pid,
|
||||
name: process.name.clone(),
|
||||
cpu_usage: process.cpu_usage_percent,
|
||||
mem_usage: process.mem_usage_percent,
|
||||
group_pids: vec![process.pid],
|
||||
read_per_sec,
|
||||
write_per_sec,
|
||||
total_read,
|
||||
total_write,
|
||||
process_states: process.process_state.clone(),
|
||||
});
|
||||
return Some(process);
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
None
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::result;
|
||||
use std::{borrow::Cow, result};
|
||||
|
||||
/// A type alias for handling errors related to Bottom.
|
||||
pub type Result<T> = result::Result<T, BottomError>;
|
||||
|
@ -22,6 +22,8 @@ pub enum BottomError {
|
|||
ConfigError(String),
|
||||
/// An error to represent errors with converting between data types.
|
||||
ConversionError(String),
|
||||
/// An error to represent errors with querying.
|
||||
QueryError(Cow<'static, str>),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for BottomError {
|
||||
|
@ -47,6 +49,9 @@ impl std::fmt::Display for BottomError {
|
|||
BottomError::ConversionError(ref message) => {
|
||||
write!(f, "unable to convert: {}", message)
|
||||
}
|
||||
BottomError::QueryError(ref _message) => {
|
||||
write!(f, "invalid query - this should not be shown!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -98,3 +103,9 @@ impl From<std::str::Utf8Error> for BottomError {
|
|||
BottomError::ConversionError(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<regex::Error> for BottomError {
|
||||
fn from(err: regex::Error) -> Self {
|
||||
BottomError::QueryError(err.to_string().into())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#[cfg(debug_assertions)]
|
||||
pub fn init_logger() -> Result<(), fern::InitError> {
|
||||
fern::Dispatch::new()
|
||||
.format(|out, message, record| {
|
||||
|
|
Loading…
Reference in a new issue