summaryrefslogtreecommitdiffstats
path: root/src
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 /src
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.
Diffstat (limited to 'src')
-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
17 files changed, 750 insertions, 946 deletions
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,
search_modifiers: SearchModifiers::default(),
@@ -336,8 +341,12 @@ impl ProcessManager {
self
}
- fn set_tree_mode(&mut self, in_tree_mode: bool) {
- self.in_tree_mode = in_tree_mode;
+ fn set_tree_mode(&mut self, tree_mode: bool) {
+ self.manager_mode = if tree_mode {
+ ManagerMode::Tree(TreeData::default())
+ } else {
+ ManagerMode::Normal
+ };
}
/// Sets whether to show the scroll index.
@@ -409,39 +418,58 @@ impl ProcessManager {
ComponentEventResult::Signal(ReturnSignal::Update)
}
- fn is_grouped(&self) -> bool {
- matches!(
- self.process_table.columns()[0].sort_type,
- ProcessSortType::Count
- )
+ fn disable_grouped(&mut self) {
+ self.manager_mode = ManagerMode::Normal;
+ self.process_table
+ .set_column(ProcessSortColumn::new(ProcessSortType::Pid), 0);
+
+ self.process_table
+ .add_column(ProcessSortColumn::new(ProcessSortType::State), 8);
+ #[cfg(target_family = "unix")]
+ {
+ self.process_table
+ .add_column(ProcessSortColumn::new(ProcessSortType::User), 8);
+ }
+ }
+
+ fn enable_grouped(&mut self) {
+ self.manager_mode = ManagerMode::Grouped;
+ self.process_table
+ .set_column(ProcessSortColumn::new(ProcessSortType::Count), 0);
+
+ #[cfg(target_family = "unix")]
+ {
+ self.process_table.remove_column(9, Some(2));
+ }
+ self.process_table.remove_column(8, Some(2));
}
fn toggle_grouped(&mut self) -> ComponentEventResult {
- if self.is_grouped() {
- self.process_table
- .set_column(ProcessSortColumn::new(ProcessSortType::Pid), 0);
+ match self.manager_mode {
+ ManagerMode::Grouped => self.disable_grouped(),
+ ManagerMode::Normal | ManagerMode::Tree { .. } => self.enable_grouped(),
+ }
- self.process_table
- .add_column(ProcessSortColumn::new(ProcessSortType::State), 8);
- #[cfg(target_family = "unix")]
- {
- self.process_table
- .add_column(ProcessSortColumn::new(ProcessSortType::User), 8);
- }
- } else {
- self.process_table
- .set_column(ProcessSortColumn::new(ProcessSortType::Count), 0);
+ self.process_table.invalidate_cached_columns();
+ ComponentEventResult::Signal(ReturnSignal::Update)
+ }
- #[cfg(target_family = "unix")]
- {
- self.process_table.remove_column(9, Some(2));
+ /// Toggles tree mode.
+ fn toggle_tree_mode(&mut self) -> ComponentEventResult {
+ match self.manager_mode {
+ ManagerMode::Normal => {
+ self.set_tree_mode(true);
+ }
+ ManagerMode::Grouped => {
+ self.disable_grouped();
+ self.set_tree_mode(true);
+ }
+ ManagerMode::Tree { .. } => {
+ self.set_tree_mode(false);
}
- self.process_table.remove_column(8, Some(2));
}
- // Invalidate row cache.
self.process_table.invalidate_cached_columns();
-
ComponentEventResult::Signal(ReturnSignal::Update)
}
@@ -497,10 +525,493 @@ impl ProcessManager {
ComponentEventResult::Signal(ReturnSignal::Update)
}
- /// Toggles tree mode.
- fn toggle_tree_mode(&mut self) -> ComponentEventResult {
- self.in_tree_mode = !self.in_tree_mode;
- ComponentEventResult::Signal(ReturnSignal::Update)
+ /// Returns whether a [`ProcessHarvest`] matches the [`ProcessManager`]'s query. If there
+ /// is no query then it will always return true.
+ fn does_process_match_query(&self, process: &ProcessHarvest) -> bool {
+ if let Some(Ok(query)) = &self.process_filter {
+ query.check(process, self.is_using_command())
+ } else {
+ true
+ }
+ }
+
+ fn get_display_tree(
+ &self, tree_data: &TreeData, data_collection: &DataCollection,
+ ) -> TextTableData {
+ fn build_tree(
+ manager: &ProcessManager, data_collection: &DataCollection,
+ filtered_tree: &FxHashMap<Pid, Vec<Pid>>, matching_pids: &FxHashMap<Pid, bool>,
+ current_process: &ProcessHarvest, mut prefixes: Vec<String>, is_last: bool,
+ collapsed_pids: &FxHashSet<Pid>, sorted_pids: &mut Vec<Pid>,
+ ) -> TextTableData {
+ const BRANCH_ENDING: char = '└';
+ const BRANCH_VERTICAL: char = '│';
+ const BRANCH_SPLIT: char = '├';
+ const BRANCH_HORIZONTAL: char = '─';
+
+ sorted_pids.push(current_process.pid);
+
+ let ProcessData {
+ process_harvest,
+ process_name_pid_map,
+ process_cmd_pid_map,
+ ..
+ } = &data_collection.process_data;
+ let is_disabled = !*matching_pids.get(&current_process.pid).unwrap_or(&false);
+
+ if collapsed_pids.contains(&current_process.pid) {
+ let mut queue = if let Some(children) = filtered_tree.get(&current_process.pid) {
+ children
+ .iter()
+ .filter_map(|child_pid| process_harvest.get(child_pid))
+ .collect_vec()
+ } else {
+ vec![]
+ };
+ let mut summed_process = current_process.clone();
+
+ while let Some(process) = queue.pop() {
+ summed_process.add(process);
+ if let Some(children) = filtered_tree.get(&process.pid) {
+ queue.extend(
+ children
+ .iter()
+ .filter_map(|child_pid| process_harvest.get(child_pid))
+ .collect_vec(),
+ );
+ }
+ }
+
+ let prefix = if prefixes.is_empty() {
+ "+ ".to_string()
+ } else {
+ format!(
+ "{}{}{} + ",
+ prefixes.join(""),
+ if is_last { BRANCH_ENDING } else { BRANCH_SPLIT },
+ BRANCH_HORIZONTAL
+ )
+ };
+
+ let process_text = manager.process_to_text(
+ &summed_process,
+ process_cmd_pid_map,
+ process_name_pid_map,
+ prefix,
+ is_disabled,
+ );
+
+ vec![process_text]
+ } else {
+ let prefix = if prefixes.is_empty() {
+ String::default()
+ } else {
+ format!(
+ "{}{}{} ",
+ prefixes.join(""),
+ if is_last { BRANCH_ENDING } else { BRANCH_SPLIT },
+ BRANCH_HORIZONTAL
+ )
+ };
+
+ let process_text = manager.process_to_text(
+ current_process,
+ process_cmd_pid_map,
+ process_name_pid_map,
+ prefix,
+ is_disabled,
+ );
+
+ if let Some(children) = filtered_tree.get(&current_process.pid) {
+ if prefixes.is_empty() {
+ prefixes.push(String::default());
+ } else {
+ prefixes.push(if is_last {
+ " ".to_string()
+ } else {
+ format!("{} ", BRANCH_VERTICAL)
+ });
+ }
+
+ let mut children = children
+ .iter()
+ .filter_map(|child_pid| process_harvest.get(child_pid))
+ .collect_vec();
+ manager.sort_process_vec(&mut children, data_collection);
+ let children_length = children.len();
+ let children_text = children
+ .into_iter()
+ .enumerate()
+ .map(|(itx, child_process)| {
+ bui