diff options
Diffstat (limited to 'src/app/widgets')
-rw-r--r-- | src/app/widgets/battery_widget.rs | 5 | ||||
-rw-r--r-- | src/app/widgets/cpu_graph.rs | 204 | ||||
-rw-r--r-- | src/app/widgets/disk_table.rs | 197 | ||||
-rw-r--r-- | src/app/widgets/mem_graph.rs | 15 | ||||
-rw-r--r-- | src/app/widgets/net_graph.rs | 15 | ||||
-rw-r--r-- | src/app/widgets/process_table.rs | 958 | ||||
-rw-r--r-- | src/app/widgets/process_table/proc_widget_column.rs | 123 | ||||
-rw-r--r-- | src/app/widgets/process_table/proc_widget_data.rs | 282 | ||||
-rw-r--r-- | src/app/widgets/process_table/sort_table.rs | 42 | ||||
-rw-r--r-- | src/app/widgets/temperature_table.rs | 138 |
10 files changed, 0 insertions, 1979 deletions
diff --git a/src/app/widgets/battery_widget.rs b/src/app/widgets/battery_widget.rs deleted file mode 100644 index 68bbca79..00000000 --- a/src/app/widgets/battery_widget.rs +++ /dev/null @@ -1,5 +0,0 @@ -#[derive(Default)] -pub struct BatteryWidgetState { - pub currently_selected_battery_index: usize, - pub tab_click_locs: Option<Vec<((u16, u16), (u16, u16))>>, -} diff --git a/src/app/widgets/cpu_graph.rs b/src/app/widgets/cpu_graph.rs deleted file mode 100644 index 7f0c1495..00000000 --- a/src/app/widgets/cpu_graph.rs +++ /dev/null @@ -1,204 +0,0 @@ -use std::{borrow::Cow, time::Instant}; - -use concat_string::concat_string; -use tui::{style::Style, text::Text, widgets::Row}; - -use crate::{ - app::{data_harvester::cpu::CpuDataType, AppConfigFields}, - canvas::{canvas_colours::CanvasColours, Painter}, - components::data_table::{ - Column, ColumnHeader, DataTable, DataTableColumn, DataTableProps, DataTableStyling, - DataToCell, - }, - data_conversion::CpuWidgetData, - utils::gen_util::truncate_text, -}; - -#[derive(Default)] -pub struct CpuWidgetStyling { - pub all: Style, - pub avg: Style, - pub entries: Vec<Style>, -} - -impl CpuWidgetStyling { - fn from_colours(colours: &CanvasColours) -> Self { - let entries = if colours.cpu_colour_styles.is_empty() { - vec![Style::default()] - } else { - colours.cpu_colour_styles.clone() - }; - - Self { - all: colours.all_colour_style, - avg: colours.avg_colour_style, - entries, - } - } -} - -pub enum CpuWidgetColumn { - CPU, - Use, -} - -impl ColumnHeader for CpuWidgetColumn { - fn text(&self) -> Cow<'static, str> { - match self { - CpuWidgetColumn::CPU => "CPU".into(), - CpuWidgetColumn::Use => "Use%".into(), - } - } -} - -pub enum CpuWidgetTableData { - All, - Entry { - data_type: CpuDataType, - last_entry: f64, - }, -} - -impl CpuWidgetTableData { - pub fn from_cpu_widget_data(data: &CpuWidgetData) -> CpuWidgetTableData { - match data { - CpuWidgetData::All => CpuWidgetTableData::All, - CpuWidgetData::Entry { - data_type, - data: _, - last_entry, - } => CpuWidgetTableData::Entry { - data_type: *data_type, - last_entry: *last_entry, - }, - } - } -} - -impl DataToCell<CpuWidgetColumn> for CpuWidgetTableData { - fn to_cell<'a>(&'a self, column: &CpuWidgetColumn, calculated_width: u16) -> Option<Text<'a>> { - const CPU_HIDE_BREAKPOINT: u16 = 5; - - // This is a bit of a hack, but apparently we can avoid having to do any fancy checks - // of showing the "All" on a specific column if the other is hidden by just always - // showing it on the CPU (first) column - if there isn't room for it, it will just collapse - // down. - // - // This is the same for the use percentages - we just *always* show them, and *always* hide the CPU column if - // it is too small. - match &self { - CpuWidgetTableData::All => match column { - CpuWidgetColumn::CPU => Some(truncate_text("All", calculated_width)), - CpuWidgetColumn::Use => None, - }, - CpuWidgetTableData::Entry { - data_type, - last_entry, - } => match column { - CpuWidgetColumn::CPU => { - if calculated_width == 0 { - None - } else { - match data_type { - CpuDataType::Avg => Some(truncate_text("AVG", calculated_width)), - CpuDataType::Cpu(index) => { - let index_str = index.to_string(); - let text = if calculated_width < CPU_HIDE_BREAKPOINT { - truncate_text(&index_str, calculated_width) - } else { - truncate_text( - &concat_string!("CPU", index_str), - calculated_width, - ) - }; - - Some(text) - } - } - } - } - CpuWidgetColumn::Use => Some(truncate_text( - &format!("{:.0}%", last_entry.round()), - calculated_width, - )), - }, - } - } - - #[inline(always)] - fn style_row<'a>(&self, row: Row<'a>, painter: &Painter) -> Row<'a> { - let style = match self { - CpuWidgetTableData::All => painter.colours.all_colour_style, - CpuWidgetTableData::Entry { - data_type, - last_entry: _, - } => match data_type { - CpuDataType::Avg => painter.colours.avg_colour_style, - CpuDataType::Cpu(index) => { - painter.colours.cpu_colour_styles - [index % painter.colours.cpu_colour_styles.len()] - } - }, - }; - - row.style(style) - } - - fn column_widths<C: DataTableColumn<CpuWidgetColumn>>( - _data: &[Self], _columns: &[C], - ) -> Vec<u16> - where - Self: Sized, - { - vec![1, 3] - } -} - -pub struct CpuWidgetState { - pub current_display_time: u64, - pub is_legend_hidden: bool, - pub show_avg: bool, - pub autohide_timer: Option<Instant>, - pub table: DataTable<CpuWidgetTableData, CpuWidgetColumn>, - pub styling: CpuWidgetStyling, -} - -impl CpuWidgetState { - pub fn new( - config: &AppConfigFields, current_display_time: u64, autohide_timer: Option<Instant>, - colours: &CanvasColours, - ) -> Self { - const COLUMNS: [Column<CpuWidgetColumn>; 2] = [ - Column::soft(CpuWidgetColumn::CPU, Some(0.5)), - Column::soft(CpuWidgetColumn::Use, Some(0.5)), - ]; - - let props = DataTableProps { - title: None, - table_gap: config.table_gap, - left_to_right: false, - is_basic: false, - show_table_scroll_position: false, // TODO: Should this be possible? - show_current_entry_when_unfocused: true, - }; - - let styling = DataTableStyling::from_colours(colours); - - CpuWidgetState { - current_display_time, - is_legend_hidden: false, - show_avg: config.show_average_cpu, - autohide_timer, - table: DataTable::new(COLUMNS, props, styling), - styling: CpuWidgetStyling::from_colours(colours), - } - } - - pub fn update_table(&mut self, data: &[CpuWidgetData]) { - self.table.set_data( - data.iter() - .map(CpuWidgetTableData::from_cpu_widget_data) - .collect(), - ); - } -} diff --git a/src/app/widgets/disk_table.rs b/src/app/widgets/disk_table.rs deleted file mode 100644 index 1d4cc604..00000000 --- a/src/app/widgets/disk_table.rs +++ /dev/null @@ -1,197 +0,0 @@ -use std::{borrow::Cow, cmp::max}; - -use kstring::KString; -use tui::text::Text; - -use crate::{ - app::AppConfigFields, - canvas::canvas_colours::CanvasColours, - components::data_table::{ - ColumnHeader, DataTableColumn, DataTableProps, DataTableStyling, DataToCell, SortColumn, - SortDataTable, SortDataTableProps, SortOrder, SortsRow, - }, - utils::gen_util::{get_decimal_bytes, sort_partial_fn, truncate_text}, -}; - -#[derive(Clone, Debug)] -pub struct DiskWidgetData { - pub name: KString, - pub mount_point: KString, - pub free_bytes: Option<u64>, - pub used_bytes: Option<u64>, - pub total_bytes: Option<u64>, - pub io_read: KString, - pub io_write: KString, -} - -impl DiskWidgetData { - pub fn free_space(&self) -> KString { - if let Some(free_bytes) = self.free_bytes { - let converted_free_space = get_decimal_bytes(free_bytes); - format!("{:.*}{}", 0, converted_free_space.0, converted_free_space.1).into() - } else { - "N/A".into() - } - } - - pub fn total_space(&self) -> KString { - if let Some(total_bytes) = self.total_bytes { - let converted_total_space = get_decimal_bytes(total_bytes); - format!( - "{:.*}{}", - 0, converted_total_space.0, converted_total_space.1 - ) - .into() - } else { - "N/A".into() - } - } - - pub fn usage(&self) -> KString { - if let (Some(used_bytes), Some(total_bytes)) = (self.used_bytes, self.total_bytes) { - format!("{:.0}%", used_bytes as f64 / total_bytes as f64 * 100_f64).into() - } else { - "N/A".into() - } - } -} - -pub enum DiskWidgetColumn { - Disk, - Mount, - Used, - Free, - Total, - IoRead, - IoWrite, -} - -impl ColumnHeader for DiskWidgetColumn { - fn text(&self) -> Cow<'static, str> { - match self { - DiskWidgetColumn::Disk => "Disk(d)", - DiskWidgetColumn::Mount => "Mount(m)", - DiskWidgetColumn::Used => "Used(u)", - DiskWidgetColumn::Free => "Free(n)", - DiskWidgetColumn::Total => "Total(t)", - DiskWidgetColumn::IoRead => "R/s(r)", - DiskWidgetColumn::IoWrite => "W/s(w)", - } - .into() - } -} - -impl DataToCell<DiskWidgetColumn> for DiskWidgetData { - fn to_cell<'a>(&'a self, column: &DiskWidgetColumn, calculated_width: u16) -> Option<Text<'a>> { - let text = match column { - DiskWidgetColumn::Disk => truncate_text(&self.name, calculated_width), - DiskWidgetColumn::Mount => truncate_text(&self.mount_point, calculated_width), - DiskWidgetColumn::Used => truncate_text(&self.usage(), calculated_width), - DiskWidgetColumn::Free => truncate_text(&self.free_space(), calculated_width), - DiskWidgetColumn::Total => truncate_text(&self.total_space(), calculated_width), - DiskWidgetColumn::IoRead => truncate_text(&self.io_read, calculated_width), - DiskWidgetColumn::IoWrite => truncate_text(&self.io_write, calculated_width), - }; - - Some(text) - } - - fn column_widths<C: DataTableColumn<DiskWidgetColumn>>( - data: &[Self], _columns: &[C], - ) -> Vec<u16> - where - Self: Sized, - { - let mut widths = vec![0; 7]; - - data.iter().for_each(|row| { - widths[0] = max(widths[0], row.name.len() as u16); - widths[1] = max(widths[1], row.mount_point.len() as u16); - }); - - widths - } -} - -pub struct DiskTableWidget { - pub table: SortDataTable<DiskWidgetData, DiskWidgetColumn>, - pub force_update_data: bool, -} - -impl SortsRow for DiskWidgetColumn { - type DataType = DiskWidgetData; - - fn sort_data(&self, data: &mut [Self::DataType], descending: bool) { - match self { - DiskWidgetColumn::Disk => { - data.sort_by(|a, b| sort_partial_fn(descending)(&a.name, &b.name)); - } - DiskWidgetColumn::Mount => { - data.sort_by(|a, b| sort_partial_fn(descending)(&a.mount_point, &b.mount_point)); - } - DiskWidgetColumn::Used => { - data.sort_by(|a, b| sort_partial_fn(descending)(&a.used_bytes, &b.used_bytes)); - } - DiskWidgetColumn::Free => { - data.sort_by(|a, b| sort_partial_fn(descending)(&a.free_bytes, &b.free_bytes)); - } - DiskWidgetColumn::Total => { - data.sort_by(|a, b| sort_partial_fn(descending)(&a.total_bytes, &b.total_bytes)); - } - DiskWidgetColumn::IoRead => { - data.sort_by(|a, b| sort_partial_fn(descending)(&a.io_read, &b.io_read)); - } - DiskWidgetColumn::IoWrite => { - data.sort_by(|a, b| sort_partial_fn(descending)(&a.io_write, &b.io_write)); - } - } - } -} - -impl DiskTableWidget { - pub fn new(config: &AppConfigFields, colours: &CanvasColours) -> Self { - let columns = [ - SortColumn::soft(DiskWidgetColumn::Disk, Some(0.2)), - SortColumn::soft(DiskWidgetColumn::Mount, Some(0.2)), - SortColumn::hard(DiskWidgetColumn::Used, 8).default_descending(), - SortColumn::hard(DiskWidgetColumn::Free, 8).default_descending(), - SortColumn::hard(DiskWidgetColumn::Total, 9).default_descending(), - SortColumn::hard(DiskWidgetColumn::IoRead, 10).default_descending(), - SortColumn::hard(DiskWidgetColumn::IoWrite, 11).default_descending(), - ]; - - let props = SortDataTableProps { - inner: DataTableProps { - title: Some(" Disks ".into()), - table_gap: config.table_gap, - left_to_right: true, - is_basic: config.use_basic_mode, - show_table_scroll_position: config.show_table_scroll_position, - show_current_entry_when_unfocused: false, - }, - sort_index: 0, - order: SortOrder::Ascending, - }; - - let styling = DataTableStyling::from_colours(colours); - - Self { - table: SortDataTable::new_sortable(columns, props, styling), - force_update_data: false, - } - } - - /// Forces an update of the data stored. - #[inline] - pub fn force_data_update(&mut self) { - self.force_update_data = true; - } - - pub fn ingest_data(&mut self, data: &[DiskWidgetData]) { - let mut data = data.to_vec(); - if let Some(column) = self.table.columns.get(self.table.sort_index()) { - column.sort_by(&mut data, self.table.order()); - } - self.table.set_data(data); - } -} diff --git a/src/app/widgets/mem_graph.rs b/src/app/widgets/mem_graph.rs deleted file mode 100644 index a86bd02a..00000000 --- a/src/app/widgets/mem_graph.rs +++ /dev/null @@ -1,15 +0,0 @@ -use std::time::Instant; - -pub struct MemWidgetState { - pub current_display_time: u64, - pub autohide_timer: Option<Instant>, -} - -impl MemWidgetState { - pub fn init(current_display_time: u64, autohide_timer: Option<Instant>) -> Self { - MemWidgetState { - current_display_time, - autohide_timer, - } - } -} diff --git a/src/app/widgets/net_graph.rs b/src/app/widgets/net_graph.rs deleted file mode 100644 index e3526210..00000000 --- a/src/app/widgets/net_graph.rs +++ /dev/null @@ -1,15 +0,0 @@ -use std::time::Instant; - -pub struct NetWidgetState { - pub current_display_time: u64, - pub autohide_timer: Option<Instant>, -} - -impl NetWidgetState { - pub fn init(current_display_time: u64, autohide_timer: Option<Instant>) -> Self { - NetWidgetState { - current_display_time, - autohide_timer, - } - } -} diff --git a/src/app/widgets/process_table.rs b/src/app/widgets/process_table.rs deleted file mode 100644 index 6cd1bda9..00000000 --- a/src/app/widgets/process_table.rs +++ /dev/null @@ -1,958 +0,0 @@ -use std::{borrow::Cow, collections::BTreeMap}; - -use const_format::formatcp; -use fxhash::{FxHashMap, FxHashSet}; -use itertools::Itertools; - -use crate::{ - app::{ - data_farmer::{DataCollection, ProcessData}, - data_harvester::processes::ProcessHarvest, - query::*, - AppConfigFields, AppSearchState, - }, - canvas::canvas_colours::CanvasColours, - components::data_table::{ - Column, ColumnHeader, ColumnWidthBounds, DataTable, DataTableColumn, DataTableProps, - DataTableStyling, SortColumn, SortDataTable, SortDataTableProps, SortOrder, SortsRow, - }, - Pid, -}; - -pub mod proc_widget_column; -pub use proc_widget_column::*; - -pub mod proc_widget_data; -pub use proc_widget_data::*; - -mod sort_table; -use sort_table::SortTableColumn; -use unicode_segmentation::GraphemeIncomplete; - -/// ProcessSearchState only deals with process' search's current settings and state. -pub struct ProcessSearchState { - pub search_state: AppSearchState, - pub is_ignoring_case: bool, - pub is_searching_whole_word: bool, - pub is_searching_with_regex: bool, -} - -impl Default for ProcessSearchState { - fn default() -> Self { - ProcessSearchState { - search_state: AppSearchState::default(), - is_ignoring_case: true, - is_searching_whole_word: false, - is_searching_with_regex: false, - } - } -} - -impl ProcessSearchState { - pub fn search_toggle_ignore_case(&mut self) { - self.is_ignoring_case = !self.is_ignoring_case; - } - - pub fn search_toggle_whole_word(&mut self) { - self.is_searching_whole_word = !self.is_searching_whole_word; - } - - pub fn search_toggle_regex(&mut self) { - self.is_searching_with_regex = !self.is_searching_with_regex; - } -} - -#[derive(Clone, Debug)] -pub enum ProcWidgetMode { - Tree { collapsed_pids: FxHashSet<Pid> }, - Grouped, - Normal, -} - -type ProcessTable = SortDataTable<ProcWidgetData, ProcColumn>; -type SortTable = DataTable<Cow<'static, str>, SortTableColumn>; -type StringPidMap = FxHashMap<String, Vec<Pid>>; - -pub struct ProcWidget { - pub mode: ProcWidgetMode, - - /// The state of the search box. - pub proc_search: ProcessSearchState, - - /// The state of the main table. - pub table: ProcessTable, - - /// The state of the togglable table that controls sorting. - pub sort_table: SortTable, - - /// A name-to-pid mapping. - pub id_pid_map: StringPidMap, - - pub is_sort_open: bool, - pub force_rerender: bool, - pub force_update_data: bool, -} - -impl ProcWidget { - pub const PID_OR_COUNT: usize = 0; - pub const PROC_NAME_OR_CMD: usize = 1; - pub const CPU: usize = 2; - pub const MEM: usize = 3; - pub const RPS: usize = 4; - pub const WPS: usize = 5; - pub const T_READ: usize = 6; - pub const T_WRITE: usize = 7; - #[cfg(target_family = "unix")] - pub const USER: usize = 8; - #[cfg(target_family = "unix")] - pub const STATE: usize = 9; - #[cfg(not(target_family = "unix"))] - pub const STATE: usize = 8; - - fn new_sort_table(config: &AppConfigFields, colours: &CanvasColours) -> SortTable { - const COLUMNS: [Column<SortTableColumn>; 1] = [Column::hard(SortTableColumn, 7)]; - - let props = DataTableProps { - title: None, - table_gap: config.table_gap, - left_to_right: true, - is_basic: false, - show_table_scroll_position: false, - show_current_entry_when_unfocused: false, - }; - - let styling = DataTableStyling::from_colours(colours); - - DataTable::new(COLUMNS, props, styling) - } - - fn new_process_table( - config: &AppConfigFields, colours: &CanvasColours, mode: &ProcWidgetMode, is_count: bool, - is_command: bool, show_memory_as_values: bool, - ) -> ProcessTable { - let (default_index, default_order) = if matches!(mode, ProcWidgetMode::Tree { .. }) { - (Self::PID_OR_COUNT, SortOrder::Ascending) - } else { - (Self::CPU, SortOrder::Descending) - }; - - let columns = { - use ProcColumn::*; - - let pid_or_count = SortColumn::new(if is_count { Count } else { Pid }); - let name_or_cmd = SortColumn::soft(if is_command { Command } else { Name }, Some(0.3)); - let cpu = SortColumn::new(CpuPercent).default_descending(); - let mem = SortColumn::new(if show_memory_as_values { - MemoryVal - } else { - MemoryPercent - }) - .default_descending(); - let rps = SortColumn::hard(ReadPerSecond, 8).default_descending(); - let wps = SortColumn::hard(WritePerSecond, 8).default_descending(); - let tr = SortColumn::hard(TotalRead, 8).default_descending(); - let tw = SortColumn::hard(TotalWrite, 8).default_descending(); - let state = SortColumn::hard(State, 7); - - vec![ - pid_or_count, - name_or_cmd, - cpu, - mem, - rps, - wps, - tr, - tw, - #[cfg(target_family = "unix")] - SortColumn::soft(User, Some(0.05)), - state, - ] - }; - - let inner_props = DataTableProps { - title: Some(" Processes ".into()), - table_gap: config.table_gap, - left_to_right: true, - is_basic: config.use_basic_mode, - show_table_scroll_position: config.show_table_scroll_position, - show_current_entry_when_unfocused: false, - }; - let props = SortDataTableProps { - inner: inner_props, - sort_index: default_index, - order: default_order, - }; - - let styling = DataTableStyling::from_colours(colours); - - DataTable::new_sortable(columns, props, styling) - } - - pub fn new( - config: &AppConfigFields, mode: ProcWidgetMode, is_case_sensitive: bool, - is_match_whole_word: bool, is_use_regex: bool, show_memory_as_values: bool, - is_command: bool, colours: &CanvasColours, - ) -> Self { - let process_search_state = { - let mut pss = ProcessSearchState::default(); - - if is_case_sensitive { - // By default it's off - pss.search_toggle_ignore_case(); - } - if is_match_whole_word { - pss.search_toggle_whole_word(); - } - if is_use_regex { - pss.search_toggle_regex(); - } - - pss - }; - - let is_count = matches!(mode, ProcWidgetMode::Grouped); - let sort_table = Self::new_sort_table(config, colours); - let table = Self::new_process_table( - config, - colours, - &mode, - is_count, - is_command, - show_memory_as_values, - ); - - let id_pid_map = FxHashMap::default(); - - let mut table = ProcWidget { - proc_search: process_search_state, - table, - sort_table, - id_pid_map, - is_sort_open: false, - mode, - force_rerender: true, - force_update_data: false, - }; - table.sort_table.set_data(table.column_text()); - - table - } - - pub fn is_using_command(&self) -> bool { - self.table - .columns - .get(ProcWidget::PROC_NAME_OR_CMD) - .map(|col| matches!(col.inner(), ProcColumn::Command)) - .unwrap_or(false) - } - - pub fn is_mem_percent(&self) -> bool { - self.table - .columns - .get(ProcWidget::MEM) - .map(|col| matches!(col.inner(), ProcColumn::MemoryPercent)) - .unwrap_or(false) - } - - fn get_query(&self) -> &Option<Query> { - if self.proc_search.search_state.is_invalid_or_blank_search() { - &None - } else { - &self.proc_search.search_state.query - } - } - - /// This function *only* updates the displayed process data. If there is a need to update the actual *stored* data, - /// call it before this function. - pub fn ingest_data(&mut self, data_collection: &DataCollection) { - let data = match &self.mode { - ProcWidgetMode::Grouped | ProcWidgetMode::Normal => { - self.get_normal_data(&data_collection.process_data.process_harvest) - } - ProcWidgetMode::Tree { collapsed_pids } => { - self.get_tree_data(collapsed_pids, data_collection) - } - }; - self.table.set_data(data); - } - - fn get_tree_data( - &self, collapsed_pids: &FxHashSet<Pid>, data_collection: &DataCollection, - ) -> Vec<ProcWidgetData> { - const BRANCH_END: char = '└'; - const BRANCH_VERTICAL: char = '│'; - const BRANCH_SPLIT: char = '├'; - const BRANCH_HORIZONTAL: char = '─'; - - let search_query = self.get_query(); - let is_using_command = self.is_using_command(); - let is_mem_percent = self.is_mem_percent(); - - let ProcessData { - process_harvest, - process_parent_mapping, - orphan_pids, - .. - } = &data_collection.process_data; - - let kept_pids = data_collection - .process_data - .process_harvest - .iter() - .map(|(pid, process)| { - ( - *pid, - search_query - .as_ref() - .map(|q| q.check(process, is_using_command)) - .unwrap_or(true), - ) - }) - .collect::<FxHashMap<_, _>>(); - - let filtered_tree = { - let mut filtered_tree = FxHashMap::default(); - - // We do a simple BFS traversal to build our filtered parent-to-tree mappings. - let mut visited_pids = FxHashMap::default(); - let mut stack = orphan_pids - .iter() - .filter_map(|process| process_harvest.get(process)) - .collect_vec(); - - while let Some(process) = stack.last() { - let is_process_matching = *kept_pids.get(&process.pid).unwrap_or(&false); - - if let Some(children_pids) = process_parent_mapping.get(&process.pid) { - if children_pids - .iter() - .all(|pid| visited_pids.contains_key(pid)) - { - let shown_children = children_pids - .iter() - .filter(|pid| visited_pids.get(*pid).copied().unwrap_or(false)) - .collect_vec(); - let is_shown = is_process_matching || !shown_children.is_empty(); - visited_pids.insert(process.pid, is_shown); - - if is_shown { - filtered_tree.insert( - process.pid, - shown_children - .into_iter() - .filter_map(|pid| { - process_harvest.get(pid).map(|process| process.pid) - }) - .collect_vec(), - ); - } - - stack.pop(); - } else { - children_pids - .iter() - .filter_map(|process| process_harvest.get(process)) - .rev() - .for_each(|process| { - stack.push(process); - }); - } - } else { - if is_process_matching { - filtered_tree.insert(process.pid, vec![]); - } - - visited_pids.insert(process.pid, is_process_matching); - stack.pop(); - } - } - - filtered_tree - }; - - let mut data = vec![]; - let mut prefixes = vec![]; - let mut stack = orphan_pids - .iter() - .filter_map(|pid| { - if filtered_tree.contains_key(pid) { - process_harvest.get(pid).map(|process| { - ProcWidgetData::from_data(process, is_using_command, is_mem_percent) - }) - } else { - None - } - }) - .collect_vec(); - - stack.sort_unstable_by_key(|p| p.pid); - - let column = self.table.columns.get(self.table.sort_index()).unwrap(); - sort_skip_pid_asc(column.inner(), &mut stack, self.table.order()); - - stack.reverse(); - - let mut length_stack = vec![stack.len()]; - - while let (Some(process), Some(siblings_left)) = (stack.pop(), length_stack.last_mut()) { - *siblings_left -= 1; - - let disabled = !*kept_pids.get(&process.pid).unwrap_or(&false); - let is_last = *siblings_left == 0; - - if collapsed_pids.contains(&process.pid) { - let mut summed_process = process.clone(); - - if let Some(children_pids) = filtered_tree.get(&process.pid) { - let mut sum_queue = children_pids - .iter() - .filter_map(|child| { - process_harvest.get(child).map(|p| { - ProcWidgetData::from_data(p, is_using_command, is_mem_percent) - }) - }) - .collect_vec(); - - while let Some(process) = sum_queue.pop() { - summed_process.add(&process); - - if let Some(pids) = filtered_tree.get(&process.pid) { - sum_queue.extend(pids.iter().filter_map(|child| { - process_harvest.get(child).map(|p| { - ProcWidgetData::from_data(p, is_using_command, is_mem_percent) - }) - })); - } - } - } - - let prefix = if prefixes.is_empty() { - "+ ".to_string() - } else { - format!( - "{}{}{} + ", - prefixes.join(""), - if is_last { BRANCH_END } else { BRANCH_SPLIT }, - BRANCH_HORIZONTAL - ) - }; - - data.push(summed_process.prefix(Some(prefix)).disabled(disabled)); - } else { - let prefix = if prefixes.is_empty() { - String::default() - } else { - format!( - "{}{}{} ", - prefixes.join(""), - if is_last { BRANCH_END } else { BRANCH_SPLIT }, - BRANCH_HORIZONTAL - ) - }; - let pid = process.pid; - data.push(process.prefix(Some(prefix)).disabled(disabled)); - - if let Some(children_pids) = filtered_tree.get(&pid) { - if prefixes.is_empty() { - prefixes.push(""); - } else { - prefixes.push(if is_last { - " " - } else { - formatcp!("{} ", BRANCH_VERTICAL) - }); - } - - let mut children = children_pids - .iter() - .filter_map(|child_pid| { - process_harvest.get(child_pid).map(|p| { - ProcWidgetData::from_data(p, is_using_command, is_mem_percent) - }) - }) - .collect_vec(); - - column.sort_by(&mut children, self.table.order().rev()); - - length_stack.push(children.len()); - stack.extend(children); - } - } - - while let Some(children_left) = length_stack.last() { - if *children_left == 0 { - length_stack.pop(); - prefixes.pop(); - } else { - break; - } - |