summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorClementTsang <cjhtsang@uwaterloo.ca>2020-05-01 23:53:29 -0400
committerClementTsang <cjhtsang@uwaterloo.ca>2020-05-02 16:01:07 -0400
commit6e81fbeebf7de91bc319b2c90cd25f18fdf288ea (patch)
treeff232af2dc4ab2705496000c779c9fd52c371ea5
parentbb45763b39cf7a8047b0c499e6d1a2ac5f8d0847 (diff)
change: more advanced searching and filtering
-rw-r--r--CHANGELOG.md8
-rw-r--r--CONTRIBUTING.md3
-rw-r--r--Cargo.lock18
-rw-r--r--Cargo.toml7
-rw-r--r--src/app.rs116
-rw-r--r--src/app/query.rs267
-rw-r--r--src/app/states.rs308
-rw-r--r--src/canvas.rs3
-rw-r--r--src/canvas/widgets/cpu_graph.rs3
-rw-r--r--src/canvas/widgets/process_table.rs69
-rw-r--r--src/data_conversion.rs48
-rw-r--r--src/main.rs91
-rw-r--r--src/utils/error.rs13
-rw-r--r--src/utils/logging.rs1
14 files changed, 674 insertions, 281 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 72b604aa..563b0efb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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).
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index c616a359..4058e944 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -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).
diff --git a/Cargo.lock b/Cargo.lock
index 72666d85..ae20694c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index 58b5766a..ab5a1a70 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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"
diff --git a/src/app.rs b/src/app.rs
index de9e8145..ff39f06e 100644
--- a/src/app.rs
+++ b/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
diff --git a/src/app/query.rs b/src/app/query.rs
new file mode 100644
index 00000000..cfb8a571
--- /dev/null
+++ b/src/app/query.rs
@@ -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,
+}
diff --git a/src/app/states.rs b/src/app/states.rs
index ca181502..825a08fa 100644
--- a/src/app/states.rs
+++ b/src/app/states.rs
@@ -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 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 = false;
} 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);
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 = 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,
+ },
+