summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorClementTsang <cjhtsang@uwaterloo.ca>2021-11-20 22:45:49 -0500
committerClementTsang <cjhtsang@uwaterloo.ca>2021-11-20 22:45:49 -0500
commitcc66f1fcaea6d31581efea7dfc1b0029e4754f49 (patch)
tree4d9c2f0a6d9ddaefd73e95caddfa9d356e173e89
parent5833cb8ad1f30d42efd9dc7f5f27bed35882bc95 (diff)
refactor: mostly add back tree mode for process
Mouse control on collapse is not working yet, need to do some work internally first.
-rw-r--r--Cargo.lock48
-rw-r--r--Cargo.toml6
-rw-r--r--docs/content/configuration/command-line-flags.md2
-rw-r--r--sample_configs/default_config.toml3
-rw-r--r--sample_configs/demo_config.toml2
-rw-r--r--src/app.rs12
-rw-r--r--src/app/data_farmer.rs151
-rw-r--r--src/app/data_harvester.rs2
-rw-r--r--src/app/data_harvester/processes.rs43
-rw-r--r--src/app/data_harvester/processes/linux.rs3
-rw-r--r--src/app/data_harvester/processes/macos.rs1
-rw-r--r--src/app/data_harvester/processes/windows.rs1
-rw-r--r--src/app/layout_manager.rs2
-rw-r--r--src/app/widgets/base/sort_text_table.rs7
-rw-r--r--src/app/widgets/base/text_table.rs18
-rw-r--r--src/app/widgets/bottom_widgets/process.rs836
-rw-r--r--src/app/widgets/dialogs/help.rs2
-rw-r--r--src/canvas.rs2
-rw-r--r--src/clap.rs99
-rw-r--r--src/constants.rs8
-rw-r--r--src/data_conversion.rs505
-rw-r--r--src/options.rs4
-rw-r--r--tests/arg_tests.rs42
-rw-r--r--tests/invalid_config_tests.rs22
-rw-r--r--tests/invalid_configs/invalid_default_widget_count.toml3
-rw-r--r--tests/invalid_configs/lone_default_widget_count.toml2
26 files changed, 788 insertions, 1038 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 7d6f91c7..ec194f46 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -239,7 +239,7 @@ dependencies = [
"cargo-husky",
"cfg-if",
"clap",
- "crossterm",
+ "crossterm 0.22.1",
"ctrlc",
"dirs",
"enum_dispatch",
@@ -247,7 +247,6 @@ dependencies = [
"float-ord",
"futures",
"futures-timer",
- "fxhash",
"heim",
"indexmap",
"indextree",
@@ -258,6 +257,7 @@ dependencies = [
"predicates 1.0.8",
"procfs",
"regex",
+ "rustc-hash",
"serde",
"smol",
"sysinfo",
@@ -434,7 +434,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ebde6a9dd5e331cd6c6f48253254d117642c31653baa475e394657c59c1f7d"
dependencies = [
"bitflags",
- "crossterm_winapi",
+ "crossterm_winapi 0.8.0",
+ "libc",
+ "mio",
+ "parking_lot",
+ "signal-hook",
+ "signal-hook-mio",
+ "winapi",
+]
+
+[[package]]
+name = "crossterm"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c85525306c4291d1b73ce93c8acf9c339f9b213aef6c1d85c3830cbf1c16325c"
+dependencies = [
+ "bitflags",
+ "crossterm_winapi 0.9.0",
"libc",
"mio",
"parking_lot",
@@ -453,6 +469,15 @@ dependencies = [
]
[[package]]
+name = "crossterm_winapi"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
name = "ctrlc"
version = "3.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -685,15 +710,6 @@ dependencies = [
]
[[package]]
-name = "fxhash"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
-dependencies = [
- "byteorder",
-]
-
-[[package]]
name = "getrandom"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1307,6 +1323,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
+[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1521,7 +1543,7 @@ checksum = "39c8ce4e27049eed97cfa363a5048b09d995e209994634a0efc26a14ab6c0c23"
dependencies = [
"bitflags",
"cassowary",
- "crossterm",
+ "crossterm 0.20.0",
"unicode-segmentation",
"unicode-width",
]
diff --git a/Cargo.toml b/Cargo.toml
index 20a936aa..713228cc 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -25,8 +25,6 @@ doc = false
[profile.release]
debug = 0
lto = true
-# debug = true
-# lto = false
opt-level = 3
codegen-units = 1
@@ -36,7 +34,7 @@ default = ["fern", "log", "battery"]
[dependencies]
anyhow = "1.0.40"
backtrace = "0.3.59"
-crossterm = "0.20.0"
+crossterm = "0.22.1"
ctrlc = { version = "3.1.9", features = ["termination"] }
clap = "2.33"
cfg-if = "1.0"
@@ -45,12 +43,12 @@ enum_dispatch = "0.3.7"
float-ord = "0.3.2"
futures = "0.3.14"
futures-timer = "3.0.2"
-fxhash = "0.2.1"
indexmap = "1.6.2"
indextree = "4.3.1"
itertools = "0.10.0"
once_cell = "1.5.2"
regex = "1.5.4"
+rustc-hash = "1.1.0"
serde = { version = "1.0.125", features = ["derive"] }
# Sysinfo is still used in Linux for the ProcessStatus
sysinfo = "0.18.2"
diff --git a/docs/content/configuration/command-line-flags.md b/docs/content/configuration/command-line-flags.md
index b5d6d3d3..25d0c1b8 100644
--- a/docs/content/configuration/command-line-flags.md
+++ b/docs/content/configuration/command-line-flags.md
@@ -17,8 +17,6 @@ The following flags can be provided to bottom in the command line to change the
| `-C, --config <CONFIG PATH>` | Sets the location of the config file. |
| `-u, --current_usage` | Sets process CPU% to be based on current CPU%. |
| `-t, --default_time_value <MS>` | Default time value for graphs in ms. |
-| `--default_widget_count <INT>` | Sets the n'th selected widget type as the default. |
-| `--default_widget_type <WIDGET TYPE>` | Sets the default widget type, use --help for more info. |
| `--disable_advanced_kill` | Hides advanced options to stop a process on Unix-like systems. |
| `--disable_click` | Disables mouse clicks. |
| `-m, --dot_marker` | Uses a dot marker for graphs. |
diff --git a/sample_configs/default_config.toml b/sample_configs/default_config.toml
index 1e2d56a4..556c131d 100644
--- a/sample_configs/default_config.toml
+++ b/sample_configs/default_config.toml
@@ -38,9 +38,6 @@
#time_delta = 15000
# Hides the time scale.
#hide_time = false
-# Override layout default widget
-#default_widget_type = "proc"
-#default_widget_count = 1
# Use basic mode
#basic = false
# Use the old network legend style
diff --git a/sample_configs/demo_config.toml b/sample_configs/demo_config.toml
index e6c5cf71..0874779b 100644
--- a/sample_configs/demo_config.toml
+++ b/sample_configs/demo_config.toml
@@ -11,5 +11,3 @@ group_processes = false
case_sensitive = false
whole_word = false
regex = true
-default_widget_type = "cpu"
-default_widget_count = 1
diff --git a/src/app.rs b/src/app.rs
index d81b88fa..40577e4e 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -10,8 +10,8 @@ pub mod widgets;
use std::time::Instant;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseEvent};
-use fxhash::FxHashMap;
use indextree::{Arena, NodeId};
+use rustc_hash::FxHashMap;
pub use data_farmer::*;
use data_harvester::temperature;
@@ -474,7 +474,7 @@ impl AppState {
}
#[cfg(target_family = "unix")]
- pub fn on_number(&mut self, number_char: char) {
+ fn on_number(&mut self, number_char: char) {
if self.delete_dialog_state.is_showing_dd {
if self
.delete_dialog_state
@@ -507,7 +507,7 @@ impl AppState {
}
}
- pub fn on_left_key(&mut self) {
+ fn on_left_key(&mut self) {
// if !self.is_in_dialog() {
// match self.current_widget.widget_type {
// BottomWidgetType::ProcSearch => {
@@ -566,7 +566,7 @@ impl AppState {
// }
}
- pub fn on_right_key(&mut self) {
+ fn on_right_key(&mut self) {
// if !self.is_in_dialog() {
// match self.current_widget.widget_type {
// BottomWidgetType::ProcSearch => {
@@ -626,7 +626,7 @@ impl AppState {
// }
}
- pub fn start_killing_process(&mut self) {
+ fn start_killing_process(&mut self) {
todo!()
// if let Some(proc_widget_state) = self
@@ -666,7 +666,7 @@ impl AppState {
// }
}
- pub fn kill_highlighted_process(&mut self) -> Result<()> {
+ fn kill_highlighted_process(&mut self) -> Result<()> {
// if let BottomWidgetType::Proc = self.current_widget.widget_type {
// if let Some(current_selected_processes) = &self.to_delete_process_list {
// #[cfg(target_family = "unix")]
diff --git a/src/app/data_farmer.rs b/src/app/data_farmer.rs
index 83b53d29..75edc3ea 100644
--- a/src/app/data_farmer.rs
+++ b/src/app/data_farmer.rs
@@ -1,3 +1,4 @@
+use itertools::Itertools;
/// In charge of cleaning, processing, and managing data. I couldn't think of
/// a better name for the file. Since I called data collection "harvesting",
/// then this is the farmer I guess.
@@ -13,6 +14,7 @@
/// memory usage and higher CPU usage - you will be trying to process more and
/// more points as this is used!
use once_cell::sync::Lazy;
+use rustc_hash::FxHashMap;
use std::{collections::HashMap, time::Instant, vec::Vec};
@@ -26,6 +28,8 @@ use crate::{
};
use regex::Regex;
+use super::data_harvester::processes::ProcessHarvest;
+
#[derive(Clone, Debug, Default)]
pub struct TimedData {
pub rx_data: f64,
@@ -36,6 +40,90 @@ pub struct TimedData {
pub swap_data: Option<f64>,
}
+#[derive(Clone, Debug, Default)]
+pub struct ProcessData {
+ /// A PID to process data map.
+ pub process_harvest: FxHashMap<Pid, ProcessHarvest>,
+
+ /// A mapping from a process name to any PID with that name.
+ pub process_name_pid_map: HashMap<String, Vec<Pid>>,
+
+ /// A mapping from a process command to any PID with that name.
+ pub process_cmd_pid_map: HashMap<String, Vec<Pid>>,
+
+ /// A mapping between a process PID to any children process PIDs.
+ pub process_parent_mapping: FxHashMap<Pid, Vec<Pid>>,
+
+ /// PIDs corresponding to processes that have no parents.
+ pub orphan_pids: Vec<Pid>,
+}
+
+impl ProcessData {
+ fn ingest(&mut self, list_of_processes: Vec<ProcessHarvest>) {
+ // TODO: [Optimization] Probably more efficient to all of this in the data collection step, but it's fine for now.
+ self.process_name_pid_map.clear();
+ self.process_cmd_pid_map.clear();
+ self.process_parent_mapping.clear();
+
+ // Reverse as otherwise the pid mappings are in the wrong order.
+ list_of_processes.iter().rev().for_each(|process_harvest| {
+ if let Some(entry) = self.process_name_pid_map.get_mut(&process_harvest.name) {
+ entry.push(process_harvest.pid);
+ } else {
+ self.process_name_pid_map
+ .insert(process_harvest.name.to_string(), vec![process_harvest.pid]);
+ }
+
+ if let Some(entry) = self.process_cmd_pid_map.get_mut(&process_harvest.command) {
+ entry.push(process_harvest.pid);
+ } else {
+ self.process_cmd_pid_map.insert(
+ process_harvest.command.to_string(),
+ vec![process_harvest.pid],
+ );
+ }
+
+ if let Some(parent_pid) = process_harvest.parent_pid {
+ if let Some(entry) = self.process_parent_mapping.get_mut(&parent_pid) {
+ entry.push(process_harvest.pid);
+ } else {
+ self.process_parent_mapping
+ .insert(parent_pid, vec![process_harvest.pid]);
+ }
+ }
+ });
+
+ self.process_name_pid_map.shrink_to_fit();
+ self.process_cmd_pid_map.shrink_to_fit();
+ self.process_parent_mapping.shrink_to_fit();
+
+ let process_pid_map = list_of_processes
+ .into_iter()
+ .map(|process| (process.pid, process))
+ .collect();
+ self.process_harvest = process_pid_map;
+
+ // This also needs a quick sort + reverse to be in the correct order.
+ self.orphan_pids = self
+ .process_harvest
+ .iter()
+ .filter_map(|(pid, process_harvest)| {
+ if let Some(parent_pid) = process_harvest.parent_pid {
+ if self.process_harvest.contains_key(&parent_pid) {
+ None
+ } else {
+ Some(*pid)
+ }
+ } else {
+ Some(*pid)
+ }
+ })
+ .sorted()
+ .rev()
+ .collect();
+ }
+}
+
/// AppCollection represents the pooled data stored within the main app
/// thread. Basically stores a (occasionally cleaned) record of the data
/// collected, and what is needed to convert into a displayable form.
@@ -48,16 +136,14 @@ pub struct TimedData {
/// not the data collector.
#[derive(Clone, Debug)]
pub struct DataCollection {
- pub current_instant: Instant,
+ pub current_instant: Instant, // TODO: [Refactor] Can I get rid of this? If I could, then I could just use #[derive(Default)] too!
pub timed_data_vec: Vec<(Instant, TimedData)>,
pub network_harvest: network::NetworkHarvest,
pub memory_harvest: memory::MemHarvest,
pub swap_harvest: memory::MemHarvest,
pub cpu_harvest: cpu::CpuHarvest,
pub load_avg_harvest: cpu::LoadAvgHarvest,
- pub process_harvest: Vec<processes::ProcessHarvest>,
- pub process_name_pid_map: HashMap<String, Vec<Pid>>,
- pub process_cmd_pid_map: HashMap<String, Vec<Pid>>,
+ pub process_data: ProcessData,
pub disk_harvest: Vec<disks::DiskHarvest>,
pub io_harvest: disks::IoHarvest,
pub io_labels_and_prev: Vec<((u64, u64), (u64, u64))>,
@@ -71,22 +157,20 @@ impl Default for DataCollection {
fn default() -> Self {
DataCollection {
current_instant: Instant::now(),
- timed_data_vec: Vec::default(),
- network_harvest: network::NetworkHarvest::default(),
- memory_harvest: memory::MemHarvest::default(),
- swap_harvest: memory::MemHarvest::default(),
- cpu_harvest: cpu::CpuHarvest::default(),
- load_avg_harvest: cpu::LoadAvgHarvest::default(),
- process_harvest: Vec::default(),
- process_name_pid_map: HashMap::default(),
- process_cmd_pid_map: HashMap::default(),
- disk_harvest: Vec::default(),
- io_harvest: disks::IoHarvest::default(),
- io_labels_and_prev: Vec::default(),
- io_labels: Vec::default(),
- temp_harvest: Vec::default(),
+ timed_data_vec: Default::default(),
+ network_harvest: Default::default(),
+ memory_harvest: Default::default(),
+ swap_harvest: Default::default(),
+ cpu_harvest: Default::default(),
+ load_avg_harvest: Default::default(),
+ process_data: Default::default(),
+ disk_harvest: Default::default(),
+ io_harvest: Default::default(),
+ io_labels_and_prev: Default::default(),
+ io_labels: Default::default(),
+ temp_harvest: Default::default(),
#[cfg(feature = "battery")]
- battery_harvest: Vec::default(),
+ battery_harvest: Default::default(),
}
}
}
@@ -98,9 +182,7 @@ impl DataCollection {
self.memory_harvest = Default::default();
self.swap_harvest = Default::default();
self.cpu_harvest = Default::default();
- self.process_harvest = Default::default();
- self.process_name_pid_map = Default::default();
- self.process_cmd_pid_map = Default::default();
+ self.process_data = Default::default();
self.disk_harvest = Default::default();
self.io_harvest = Default::default();
self.io_labels_and_prev = Default::default();
@@ -132,8 +214,6 @@ impl DataCollection {
pub fn eat_data(&mut self, harvested_data: Box<Data>) {
let harvested_time = harvested_data.last_collection_time;
- // trace!("Harvested time: {:?}", harvested_time);
- // trace!("New current instant: {:?}", self.current_instant);
let mut new_entry = TimedData::default();
// Network
@@ -316,28 +396,7 @@ impl DataCollection {
}
fn eat_proc(&mut self, list_of_processes: Vec<processes::ProcessHarvest>) {
- // TODO: [Optimization] Probably more efficient to do this in the data collection step, but it's fine for now.
- self.process_name_pid_map.clear();
- self.process_cmd_pid_map.clear();
- list_of_processes.iter().for_each(|process_harvest| {
- if let Some(entry) = self.process_name_pid_map.get_mut(&process_harvest.name) {
- entry.push(process_harvest.pid);
- } else {
- self.process_name_pid_map
- .insert(process_harvest.name.to_string(), vec![process_harvest.pid]);
- }
-
- if let Some(entry) = self.process_cmd_pid_map.get_mut(&process_harvest.command) {
- entry.push(process_harvest.pid);
- } else {
- self.process_cmd_pid_map.insert(
- process_harvest.command.to_string(),
- vec![process_harvest.pid],
- );
- }
- });
-
- self.process_harvest = list_of_processes;
+ self.process_data.ingest(list_of_processes);
}
#[cfg(feature = "battery")]
diff --git a/src/app/data_harvester.rs b/src/app/data_harvester.rs
index 2672022d..75d5b4b6 100644
--- a/src/app/data_harvester.rs
+++ b/src/app/data_harvester.rs
@@ -3,7 +3,7 @@
use std::time::Instant;
#[cfg(target_os = "linux")]
-use fxhash::FxHashMap;
+use rustc_hash::FxHashMap;
#[cfg(not(target_os = "linux"))]
use sysinfo::{System, SystemExt};
diff --git a/src/app/data_harvester/processes.rs b/src/app/data_harvester/processes.rs
index 1ee60123..bcbc97e0 100644
--- a/src/app/data_harvester/processes.rs
+++ b/src/app/data_harvester/processes.rs
@@ -45,41 +45,10 @@ pub enum ProcessSorting {
Count,
}
-impl std::fmt::Display for ProcessSorting {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(
- f,
- "{}",
- match &self {
- ProcessSorting::CpuPercent => "CPU%",
- ProcessSorting::MemPercent => "Mem%",
- ProcessSorting::Mem => "Mem",
- ProcessSorting::ReadPerSecond => "R/s",
- ProcessSorting::WritePerSecond => "W/s",
- ProcessSorting::TotalRead => "T.Read",
- ProcessSorting::TotalWrite => "T.Write",
- ProcessSorting::State => "State",
- ProcessSorting::ProcessName => "Name",
- ProcessSorting::Command => "Command",
- ProcessSorting::Pid => "PID",
- ProcessSorting::Count => "Count",
- ProcessSorting::User => "User",
- }
- )
- }
-}
-
-impl Default for ProcessSorting {
- fn default() -> Self {
- ProcessSorting::CpuPercent
- }
-}
-
#[derive(Debug, Clone, Default)]
pub struct ProcessHarvest {
pub pid: Pid,
pub parent_pid: Option<Pid>,
- pub children_pids: Vec<Pid>,
pub cpu_usage_percent: f64,
pub mem_usage_percent: f64,
pub mem_usage_bytes: u64,
@@ -102,3 +71,15 @@ pub struct ProcessHarvest {
#[cfg(target_family = "unix")]
pub user: Cow<'static, str>,
}
+
+impl ProcessHarvest {
+ pub(crate) fn add(&mut self, rhs: &ProcessHarvest) {
+ self.cpu_usage_percent += rhs.cpu_usage_percent;
+ self.mem_usage_bytes += rhs.mem_usage_bytes;
+ self.mem_usage_percent += rhs.mem_usage_percent;
+ self.read_bytes_per_sec += rhs.read_bytes_per_sec;
+ self.write_bytes_per_sec += rhs.write_bytes_per_sec;
+ self.total_read_bytes += rhs.total_read_bytes;
+ self.total_write_bytes += rhs.total_write_bytes;
+ }
+}
diff --git a/src/app/data_harvester/processes/linux.rs b/src/app/data_harvester/processes/linux.rs
index 5d11ca7b..63158297 100644
--- a/src/app/data_harvester/processes/linux.rs
+++ b/src/app/data_harvester/processes/linux.rs
@@ -11,7 +11,7 @@ use sysinfo::ProcessStatus;
use procfs::process::{Process, Stat};
-use fxhash::{FxHashMap, FxHashSet};
+use rustc_hash::{FxHashMap, FxHashSet};
/// Maximum character length of a /proc/<PID>/stat process name.
/// If it's equal or greater, then we instead refer to the command for the name.
@@ -203,7 +203,6 @@ fn read_proc(
ProcessHarvest {
pid: process.pid,
parent_pid,
- children_pids: vec![],
cpu_usage_percent,
mem_usage_percent,
mem_usage_bytes,
diff --git a/src/app/data_harvester/processes/macos.rs b/src/app/data_harvester/processes/macos.rs
index d5dffe85..a65f2627 100644
--- a/src/app/data_harvester/processes/macos.rs
+++ b/src/app/data_harvester/processes/macos.rs
@@ -89,7 +89,6 @@ pub fn get_process_data(
process_vector.push(ProcessHarvest {
pid: process_val.pid(),
parent_pid: process_val.parent(),
- children_pids: vec![],
name,
command,
mem_usage_percent: if mem_total_kb > 0 {
diff --git a/src/app/data_harvester/processes/windows.rs b/src/app/data_harvester/processes/windows.rs
index 763f9cf8..08cde500 100644
--- a/src/app/data_harvester/processes/windows.rs
+++ b/src/app/data_harvester/processes/windows.rs
@@ -58,7 +58,6 @@ pub fn get_process_data(
process_vector.push(ProcessHarvest {
pid: process_val.pid(),
parent_pid: process_val.parent(),
- children_pids: vec![],
name,
command,
mem_usage_percent: if mem_total_kb > 0 {
diff --git a/src/app/layout_manager.rs b/src/app/layout_manager.rs
index 7f0dd663..b5d54480 100644
--- a/src/app/layout_manager.rs
+++ b/src/app/layout_manager.rs
@@ -9,8 +9,8 @@ use crate::{
ProcessDefaults,
},
};
-use fxhash::FxHashMap;
use indextree::{Arena, NodeId};
+use rustc_hash::FxHashMap;
use std::cmp::min;
use tui::layout::Rect;
diff --git a/src/app/widgets/base/sort_text_table.rs b/src/app/widgets/base/sort_text_table.rs
index 2c2d9e21..001624a4 100644
--- a/src/app/widgets/base/sort_text_table.rs
+++ b/src/app/widgets/base/sort_text_table.rs
@@ -288,11 +288,16 @@ where
self.table.current_scroll_index()
}
- /// Returns the current column the table is sorting by.
+ /// Returns a reference to the current column the table is sorting by.
pub fn current_sorting_column(&self) -> &S {
&self.table.columns[self.sort_index]
}
+ /// Returns a mutable reference to the current column the table is sorting by.
+ pub fn current_mut_sorting_column(&mut self) -> &mut S {
+ &mut self.table.columns[self.sort_index]
+ }
+
/// Returns the current column index the table is sorting by.
pub fn current_sorting_column_index(&self) -> usize {
self.sort_index
diff --git a/src/app/widgets/base/text_table.rs b/src/app/widgets/base/text_table.rs
index d3c9c601..896eda85 100644
--- a/src/app/widgets/base/text_table.rs
+++ b/src/app/widgets/base/text_table.rs
@@ -39,8 +39,9 @@ pub trait TableColumn {
fn set_x_bounds(&mut self, x_bounds: Option<(u16, u16)>);
}
-pub type TextTableData = Vec<Vec<(Cow<'static, str>, Option<Cow<'static, str>>, Option<Style>)>>;
-pub type TextTableDataRef = [Vec<(Cow<'static, str>, Option<Cow<'static, str>>, Option<Style>)>];
+pub(crate) type TextTableRow = Vec<(Cow<'static, str>, Option<Cow<'static, str>>, Option<Style>)>;
+pub(crate) type TextTableData = Vec<TextTableRow>;
+pub(crate) type TextTableDataRef = [TextTableRow];
/// A [`SimpleColumn`] represents some column in a [`TextTable`].
#[derive(Debug)]
@@ -468,14 +469,13 @@ where
.style(painter.colours.table_header_style)
.bottom_margin(table_gap);
- let table = Table::new(rows)
+ let mut table = Table::new(rows)
.header(header)
- .style(painter.colours.text_style)
- .highlight_style(if show_selected_entry {
- painter.colours.currently_selected_text_style
- } else {
- painter.colours.text_style
- });
+ .style(painter.colours.text_style);
+
+ if show_selected_entry {
+ table = table.highlight_style(painter.colours.currently_selected_text_style);
+ }
if self.selectable {
f.render_stateful_widget(
diff --git a/src/app/widgets/bottom_widgets/process.rs b/src/app/widgets/bottom_widgets/process.rs
index 8ca3a02e..154f4fb7 100644
--- a/src/app/widgets/bottom_widgets/process.rs
+++ b/src/app/widgets/bottom_widgets/process.rs
@@ -1,13 +1,15 @@
-use std::{borrow::Cow, collections::HashMap};
+use std::{borrow::Cow, cell::RefCell, collections::HashMap};
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEvent, MouseEventKind};
use float_ord::FloatOrd;
use itertools::{Either, Itertools};
use once_cell::unsync::Lazy;
+use rustc_hash::{FxHashMap, FxHashSet};
use tui::{
backend::Backend,
layout::{Constraint, Direction, Layout, Rect},
+ style::Style,
text::{Span, Spans},
widgets::{Borders, Paragraph},
Frame,
@@ -18,14 +20,15 @@ use crate::{
data_harvester::processes::ProcessHarvest,
event::{ComponentEventResult, MultiKey, MultiKeyResult, ReturnSignal, SelectionAction},
query::*,
- text_table::DesiredColumnWidth,
+ text_table::{DesiredColumnWidth, TextTableRow},
widgets::tui_stuff::BlockBuilder,
- AppConfigFields, DataCollection,
+ AppConfigFields, DataCollection, ProcessData,
},
canvas::Painter,
- data_conversion::get_string_with_bytes,
+ data_conversion::{get_string_with_bytes, get_string_with_bytes_per_second},
options::{layout_options::LayoutRule, ProcessDefaults},
utils::error::BottomError,
+ Pid,
};
use crate::app::{
@@ -140,7 +143,7 @@ impl ProcessSortType {
ProcessSortType::Wps => Hard(Some(8)),
ProcessSortType::TotalRead => Hard(Some(7)),
ProcessSortType::TotalWrite => Hard(Some(8)),
- ProcessSortType::User => Flex(0.08),
+ ProcessSortType::User => Flex(0.08), // FIXME: [URGENT] adjust this scaling
ProcessSortType::State => Hard(Some(8)),
}
}
@@ -211,7 +214,7 @@ impl SortableColumn for ProcessSortColumn {
}
fn default_descending(&self) -> bool {
- self.sortable_column.default_descending()
+ self.sortable_column.default_descending() // TODO: [Behaviour] not sure if I like this behaviour tbh
}
fn sorting_status(&self) -> SortStatus {
@@ -239,35 +242,37 @@ impl SortableColumn for ProcessSortColumn {
}
}
+#[derive(Default)]
+struct TreeData {
+ user_collapsed_pids: FxHashSet<Pid>,
+ sorted_pids: RefCell<Vec<Pid>>,
+}
+
+enum ManagerMode {
+ Normal,
+ Grouped,
+ Tree(TreeData),
+}
+
/// A searchable, sortable table to manage processes.
pub struct ProcessManager {
bounds: Rect,
process_table: SortableTextTable<ProcessSortColumn>,
sort_menu: SortMenu,
search_block_bounds: Rect,
-
search_input: TextInput,
-
dd_multi: MultiKey,
-
selected: ProcessManagerSelection,
prev_selected: ProcessManagerSelection,
-
- in_tree_mode: bool,
+ manager_mode: ManagerMode,
show_sort: bool,
show_search: bool,
-
search_modifiers: SearchModifiers,
-
display_data: TextTableData,
-
process_filter: Option<Result<Query, BottomError>>,
-
block_border: Borders,
-
width: LayoutRule,
height: LayoutRule,
-
show_scroll_index: bool,
}
@@ -299,7 +304,7 @@ impl ProcessManager {
dd_multi: MultiKey::register(vec!['d', 'd']), // TODO: [Optimization] Maybe use something static/const/arrayvec?...
selected: ProcessManagerSelection::Processes,
prev_selected: ProcessManagerSelection::Processes,
- in_tree_mode: false,
+ manager_mode: ManagerMode::Normal,
show_sort: false,
show_search: false,
s