From 2a740f48f79498e6a812670176c45c53e0383c05 Mon Sep 17 00:00:00 2001 From: Clement Tsang <34804052+ClementTsang@users.noreply.github.com> Date: Wed, 12 Oct 2022 16:25:38 -0400 Subject: [PATCH] refactor: tables V2 (#749) * refactor: move to new data table implementation * more work towards refactor * move disk and temp over, fix longstanding bug with disk and temp if removing the last value and selected * work towards porting over CPU work towards porting over CPU fix typo partially port over cpu, fix some potentially inefficient concat_string calls more work towards cpu widget migration some refactoring * sortable data sortable data more refactoring some sort refactoring more refactoringgggg column refactoring renaming and reorganizing more refactoring regarding column logic add sort arrows again * move over sort menu * port over process port over process precommit temp temp two, remember to squash work fix broken ltr calculation and CPU hiding add back row styling temp fix a bunch of issues, get proc working more fixes around click fix frozen issues * fix dd process killing * revert some of the persistent config changes from #257 * fix colouring for trees * fix missing entries in tree * keep columns if there is no data * add and remove tests * Fix ellipsis --- Cargo.lock | 10 + Cargo.toml | 1 + clippy.toml | 3 +- src/app.rs | 340 ++--- src/app/data_farmer.rs | 45 +- src/app/data_harvester/cpu.rs | 11 +- src/app/data_harvester/cpu/heim.rs | 18 +- src/app/data_harvester/processes/linux.rs | 1 - .../processes/macos/sysctl_bindings.rs | 6 +- src/app/data_harvester/temperature.rs | 2 +- src/app/frozen_state.rs | 44 + src/app/states.rs | 54 +- src/app/widgets.rs | 17 +- src/app/widgets/cpu_graph.rs | 175 +++ src/app/widgets/disk_table.rs | 147 +++ src/app/widgets/disk_table_widget.rs | 34 - src/app/widgets/process_table.rs | 812 ++++++++++++ .../process_table/proc_widget_column.rs | 122 ++ .../widgets/process_table/proc_widget_data.rs | 274 ++++ src/app/widgets/process_table/sort_table.rs | 42 + src/app/widgets/process_table_widget.rs | 1169 ----------------- src/app/widgets/temperature_table.rs | 101 ++ src/app/widgets/temperature_table_widget.rs | 29 - src/bin/main.rs | 36 +- src/canvas.rs | 87 +- src/canvas/canvas_colours.rs | 38 +- src/canvas/widgets/cpu_basic.rs | 103 +- src/canvas/widgets/cpu_graph.rs | 139 +- src/canvas/widgets/disk_table.rs | 47 +- src/canvas/widgets/process_table.rs | 132 +- src/canvas/widgets/temp_table.rs | 44 +- src/components.rs | 3 +- src/components/data_table.rs | 242 ++++ src/components/data_table/column.rs | 257 ++++ src/components/data_table/data_type.rs | 26 + src/components/data_table/draw.rs | 289 ++++ src/components/data_table/props.rs | 21 + src/components/data_table/sortable.rs | 536 ++++++++ src/components/data_table/state.rs | 90 ++ src/components/data_table/styling.rs | 26 + src/components/text_table.rs | 5 - src/components/text_table/draw.rs | 505 ------- src/components/text_table/state.rs | 678 ---------- src/components/time_graph.rs | 9 +- src/data_conversion.rs | 379 ++---- src/lib.rs | 27 +- src/options.rs | 94 +- src/units/data_units.rs | 6 + src/utils/gen_util.rs | 26 +- 49 files changed, 3797 insertions(+), 3505 deletions(-) create mode 100644 src/app/frozen_state.rs create mode 100644 src/app/widgets/cpu_graph.rs create mode 100644 src/app/widgets/disk_table.rs delete mode 100644 src/app/widgets/disk_table_widget.rs create mode 100644 src/app/widgets/process_table.rs create mode 100644 src/app/widgets/process_table/proc_widget_column.rs create mode 100644 src/app/widgets/process_table/proc_widget_data.rs create mode 100644 src/app/widgets/process_table/sort_table.rs delete mode 100644 src/app/widgets/process_table_widget.rs create mode 100644 src/app/widgets/temperature_table.rs delete mode 100644 src/app/widgets/temperature_table_widget.rs create mode 100644 src/components/data_table.rs create mode 100644 src/components/data_table/column.rs create mode 100644 src/components/data_table/data_type.rs create mode 100644 src/components/data_table/draw.rs create mode 100644 src/components/data_table/props.rs create mode 100644 src/components/data_table/sortable.rs create mode 100644 src/components/data_table/state.rs create mode 100644 src/components/data_table/styling.rs delete mode 100644 src/components/text_table.rs delete mode 100644 src/components/text_table/draw.rs delete mode 100644 src/components/text_table/state.rs diff --git a/Cargo.lock b/Cargo.lock index d757044a..66bb93a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -225,6 +225,7 @@ dependencies = [ "heim", "indexmap", "itertools", + "kstring", "libc", "log", "mach2", @@ -906,6 +907,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +[[package]] +name = "kstring" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3066350882a1cd6d950d055997f379ac37fd39f81cd4d8ed186032eb3c5747" +dependencies = [ + "static_assertions", +] + [[package]] name = "lazy_static" version = "1.4.0" diff --git a/Cargo.toml b/Cargo.toml index d2c8386e..9c4997c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,6 +67,7 @@ futures-timer = "3.0.2" fxhash = "0.2.1" indexmap = "1.8.1" itertools = "0.10.3" +kstring = { version = "2.0.0", features = ["arc"] } log = { version = "0.4.16", optional = true } nvml-wrapper = { version = "0.7.0", optional = true } once_cell = "1.5.2" diff --git a/clippy.toml b/clippy.toml index b3a62dba..e25ae33d 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,2 +1,3 @@ cognitive-complexity-threshold = 100 -type-complexity-threshold = 500 \ No newline at end of file +type-complexity-threshold = 500 +too-many-arguments-threshold = 8 diff --git a/src/app.rs b/src/app.rs index 87e82582..a5e5c108 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,7 +1,6 @@ use std::{ cmp::{max, min}, collections::HashMap, - path::PathBuf, time::Instant, }; @@ -16,12 +15,8 @@ use layout_manager::*; pub use states::*; use crate::{ - components::text_table::SortState, constants, data_conversion::ConvertedData, - options::Config, - options::ConfigFlags, - options::WidgetIdEnabled, units::data_units::DataUnit, utils::error::{BottomError, Result}, Pid, @@ -31,12 +26,15 @@ use self::widgets::{ProcWidget, ProcWidgetMode}; pub mod data_farmer; pub mod data_harvester; +pub mod frozen_state; pub mod layout_manager; mod process_killer; pub mod query; pub mod states; pub mod widgets; +use frozen_state::FrozenState; + const MAX_SEARCH_LENGTH: usize = 200; #[derive(Debug, Clone)] @@ -45,9 +43,15 @@ pub enum AxisScaling { Linear, } +impl Default for AxisScaling { + fn default() -> Self { + AxisScaling::Log + } +} + /// AppConfigFields is meant to cover basic fields that would normally be set /// by config files or launch options. -#[derive(Debug)] +#[derive(Debug, Default)] pub struct AppConfigFields { pub update_rate_in_milliseconds: u64, pub temperature_type: temperature::TemperatureType, @@ -95,14 +99,15 @@ pub struct App { #[builder(default, setter(skip))] second_char: Option, + // FIXME: The way we do deletes is really gross. #[builder(default, setter(skip))] pub dd_err: Option, #[builder(default, setter(skip))] to_delete_process_list: Option<(String, Vec)>, - #[builder(default = false, setter(skip))] - pub is_frozen: bool, + #[builder(default, setter(skip))] + pub frozen_state: FrozenState, #[builder(default = Instant::now(), setter(skip))] last_key_press: Instant, @@ -148,8 +153,6 @@ pub struct App { pub current_widget: BottomWidget, pub used_widgets: UsedWidgets, pub filters: DataFilters, - pub config: Config, // TODO: Is this even used...? - pub config_path: Option, // TODO: Is this even used...? } #[cfg(target_os = "windows")] @@ -184,7 +187,7 @@ impl App { self.dd_err = None; // Unfreeze. - self.is_frozen = false; + self.frozen_state.thaw(); // Reset zoom self.reset_cpu_zoom(); @@ -293,25 +296,13 @@ impl App { // Allow usage whilst only in processes if !self.ignore_normal_keybinds() { - match self.current_widget.widget_type { - BottomWidgetType::Cpu => { - if let Some(cpu_widget_state) = self - .cpu_state - .get_mut_widget_state(self.current_widget.widget_id) - { - cpu_widget_state.is_multi_graph_mode = - !cpu_widget_state.is_multi_graph_mode; - } + if let BottomWidgetType::Proc = self.current_widget.widget_type { + if let Some(proc_widget_state) = self + .proc_state + .get_mut_widget_state(self.current_widget.widget_id) + { + proc_widget_state.on_tab(); } - BottomWidgetType::Proc => { - if let Some(proc_widget_state) = self - .proc_state - .get_mut_widget_state(self.current_widget.widget_id) - { - proc_widget_state.toggle_tab(); - } - } - _ => {} } } } @@ -338,7 +329,7 @@ impl App { } } - pub fn toggle_sort(&mut self) { + pub fn toggle_sort_menu(&mut self) { let widget_id = self.current_widget.widget_id - match &self.current_widget.widget_type { BottomWidgetType::Proc => 0, @@ -352,12 +343,7 @@ impl App { // If the sort is now open, move left. Otherwise, if the proc sort was selected, force move right. if pws.is_sort_open { - if let SortState::Sortable(st) = &pws.table_state.sort_state { - pws.sort_table_state.scroll_bar = 0; - pws.sort_table_state.current_scroll_position = st - .current_index - .clamp(0, pws.num_enabled_columns().saturating_sub(1)); - } + pws.sort_table.set_position(pws.table.sort_index()); self.move_widget_selection(&WidgetDirection::Left); } else if let BottomWidgetType::ProcSort = self.current_widget.widget_type { self.move_widget_selection(&WidgetDirection::Right); @@ -376,13 +362,9 @@ impl App { _ => 0, }; - if let Some(proc_widget_state) = self.proc_state.get_mut_widget_state(widget_id) { - if let SortState::Sortable(state) = - &mut proc_widget_state.table_state.sort_state - { - state.toggle_order(); - proc_widget_state.force_data_update(); - } + if let Some(pws) = self.proc_state.get_mut_widget_state(widget_id) { + pws.table.toggle_order(); + pws.force_data_update(); } } _ => {} @@ -410,7 +392,6 @@ impl App { pub fn toggle_ignore_case(&mut self) { let is_in_search_widget = self.is_in_search_widget(); - let mut is_case_sensitive: Option = None; if let Some(proc_widget_state) = self .proc_state .widget_states @@ -419,48 +400,12 @@ impl App { if is_in_search_widget && proc_widget_state.is_search_enabled() { proc_widget_state.proc_search.search_toggle_ignore_case(); proc_widget_state.update_query(); - - // Remember, it's the opposite (ignoring case is case "in"sensitive) - is_case_sensitive = Some(!proc_widget_state.proc_search.is_ignoring_case); - } - } - - // Also toggle it in the config file if we actually changed it. - if let Some(is_ignoring_case) = is_case_sensitive { - if let Some(flags) = &mut self.config.flags { - if let Some(map) = &mut flags.search_case_enabled_widgets_map { - // Just update the map. - let mapping = map.entry(self.current_widget.widget_id - 1).or_default(); - *mapping = is_ignoring_case; - - flags.search_case_enabled_widgets = - Some(WidgetIdEnabled::create_from_hashmap(map)); - } else { - // Map doesn't exist yet... initialize ourselves. - let mut map = HashMap::default(); - map.insert(self.current_widget.widget_id - 1, is_ignoring_case); - flags.search_case_enabled_widgets = - Some(WidgetIdEnabled::create_from_hashmap(&map)); - flags.search_case_enabled_widgets_map = Some(map); - } - } else { - // Must initialize it ourselves... - let mut map = HashMap::default(); - map.insert(self.current_widget.widget_id - 1, is_ignoring_case); - - self.config.flags = Some( - ConfigFlags::builder() - .search_case_enabled_widgets(WidgetIdEnabled::create_from_hashmap(&map)) - .search_case_enabled_widgets_map(map) - .build(), - ); } } } pub fn toggle_search_whole_word(&mut self) { let is_in_search_widget = self.is_in_search_widget(); - let mut is_searching_whole_word: Option = None; if let Some(proc_widget_state) = self .proc_state .widget_states @@ -469,52 +414,12 @@ impl App { if is_in_search_widget && proc_widget_state.is_search_enabled() { proc_widget_state.proc_search.search_toggle_whole_word(); proc_widget_state.update_query(); - - is_searching_whole_word = - Some(proc_widget_state.proc_search.is_searching_whole_word); } } - - // Also toggle it in the config file if we actually changed it. - if let Some(is_searching_whole_word) = is_searching_whole_word { - if let Some(flags) = &mut self.config.flags { - if let Some(map) = &mut flags.search_whole_word_enabled_widgets_map { - // Just update the map. - let mapping = map.entry(self.current_widget.widget_id - 1).or_default(); - *mapping = is_searching_whole_word; - - flags.search_whole_word_enabled_widgets = - Some(WidgetIdEnabled::create_from_hashmap(map)); - } else { - // Map doesn't exist yet... initialize ourselves. - let mut map = HashMap::default(); - map.insert(self.current_widget.widget_id - 1, is_searching_whole_word); - flags.search_whole_word_enabled_widgets = - Some(WidgetIdEnabled::create_from_hashmap(&map)); - flags.search_whole_word_enabled_widgets_map = Some(map); - } - } else { - // Must initialize it ourselves... - let mut map = HashMap::default(); - map.insert(self.current_widget.widget_id - 1, is_searching_whole_word); - - self.config.flags = Some( - ConfigFlags::builder() - .search_whole_word_enabled_widgets(WidgetIdEnabled::create_from_hashmap( - &map, - )) - .search_whole_word_enabled_widgets_map(map) - .build(), - ); - } - - // self.did_config_fail_to_save = self.update_config_file().is_err(); - } } pub fn toggle_search_regex(&mut self) { let is_in_search_widget = self.is_in_search_widget(); - let mut is_searching_with_regex: Option = None; if let Some(proc_widget_state) = self .proc_state .widget_states @@ -523,41 +428,6 @@ impl App { if is_in_search_widget && proc_widget_state.is_search_enabled() { proc_widget_state.proc_search.search_toggle_regex(); proc_widget_state.update_query(); - - is_searching_with_regex = - Some(proc_widget_state.proc_search.is_searching_with_regex); - } - } - - // Also toggle it in the config file if we actually changed it. - if let Some(is_searching_whole_word) = is_searching_with_regex { - if let Some(flags) = &mut self.config.flags { - if let Some(map) = &mut flags.search_regex_enabled_widgets_map { - // Just update the map. - let mapping = map.entry(self.current_widget.widget_id - 1).or_default(); - *mapping = is_searching_whole_word; - - flags.search_regex_enabled_widgets = - Some(WidgetIdEnabled::create_from_hashmap(map)); - } else { - // Map doesn't exist yet... initialize ourselves. - let mut map = HashMap::default(); - map.insert(self.current_widget.widget_id - 1, is_searching_whole_word); - flags.search_regex_enabled_widgets = - Some(WidgetIdEnabled::create_from_hashmap(&map)); - flags.search_regex_enabled_widgets_map = Some(map); - } - } else { - // Must initialize it ourselves... - let mut map = HashMap::default(); - map.insert(self.current_widget.widget_id - 1, is_searching_whole_word); - - self.config.flags = Some( - ConfigFlags::builder() - .search_regex_enabled_widgets(WidgetIdEnabled::create_from_hashmap(&map)) - .search_regex_enabled_widgets_map(map) - .build(), - ); } } } @@ -1191,34 +1061,23 @@ impl App { .widget_states .get(&self.current_widget.widget_id) { - if let Some(table_row) = pws - .table_data - .data - .get(pws.table_state.current_scroll_position) - { - if let Some(col_value) = table_row.row().get(ProcWidget::PROC_NAME_OR_CMD) { - let val = col_value.main_text().to_string(); - if pws.is_using_command() { - if let Some(pids) = self.data_collection.process_data.cmd_pid_map.get(&val) - { - let current_process = (val, pids.clone()); + if let Some(current) = pws.table.current_item() { + let id = current.id.to_string(); + if let Some(pids) = pws + .id_pid_map + .get(&id) + .cloned() + .or_else(|| Some(vec![current.pid])) + { + let current_process = (id, pids); - self.to_delete_process_list = Some(current_process); - self.delete_dialog_state.is_showing_dd = true; - self.is_determining_widget_boundary = true; - } - } else if let Some(pids) = - self.data_collection.process_data.name_pid_map.get(&val) - { - let current_process = (val, pids.clone()); - - self.to_delete_process_list = Some(current_process); - self.delete_dialog_state.is_showing_dd = true; - self.is_determining_widget_boundary = true; - } + self.to_delete_process_list = Some(current_process); + self.delete_dialog_state.is_showing_dd = true; + self.is_determining_widget_boundary = true; } } } + // FIXME: This should handle errors. } pub fn on_char_key(&mut self, caught_char: char) { @@ -1382,12 +1241,7 @@ impl App { 'k' => self.on_up_key(), 'j' => self.on_down_key(), 'f' => { - self.is_frozen = !self.is_frozen; - if self.is_frozen { - self.data_collection.freeze(); - } else { - self.data_collection.thaw(); - } + self.frozen_state.toggle(&self.data_collection); } 'c' => { if let BottomWidgetType::Proc = self.current_widget.widget_type { @@ -1452,7 +1306,7 @@ impl App { '-' => self.on_minus(), '=' => self.reset_zoom(), 'e' => self.toggle_expand_widget(), - 's' => self.toggle_sort(), + 's' => self.toggle_sort_menu(), 'I' => self.invert_sort(), '%' => self.toggle_percentages(), _ => {} @@ -1979,8 +1833,7 @@ impl App { .proc_state .get_mut_widget_state(self.current_widget.widget_id) { - proc_widget_state.table_state.current_scroll_position = 0; - proc_widget_state.table_state.scroll_direction = ScrollDirection::Up; + proc_widget_state.table.set_first(); } } BottomWidgetType::ProcSort => { @@ -1988,8 +1841,7 @@ impl App { .proc_state .get_mut_widget_state(self.current_widget.widget_id - 2) { - proc_widget_state.sort_table_state.current_scroll_position = 0; - proc_widget_state.sort_table_state.scroll_direction = ScrollDirection::Up; + proc_widget_state.sort_table.set_first(); } } BottomWidgetType::Temp => { @@ -1997,8 +1849,7 @@ impl App { .temp_state .get_mut_widget_state(self.current_widget.widget_id) { - temp_widget_state.table_state.current_scroll_position = 0; - temp_widget_state.table_state.scroll_direction = ScrollDirection::Up; + temp_widget_state.table.set_first(); } } BottomWidgetType::Disk => { @@ -2006,8 +1857,7 @@ impl App { .disk_state .get_mut_widget_state(self.current_widget.widget_id) { - disk_widget_state.table_state.current_scroll_position = 0; - disk_widget_state.table_state.scroll_direction = ScrollDirection::Up; + disk_widget_state.table.set_first(); } } BottomWidgetType::CpuLegend => { @@ -2015,8 +1865,7 @@ impl App { .cpu_state .get_mut_widget_state(self.current_widget.widget_id - 1) { - cpu_widget_state.table_state.current_scroll_position = 0; - cpu_widget_state.table_state.scroll_direction = ScrollDirection::Up; + cpu_widget_state.table.set_first(); } } @@ -2038,9 +1887,7 @@ impl App { .proc_state .get_mut_widget_state(self.current_widget.widget_id) { - proc_widget_state.table_state.current_scroll_position = - proc_widget_state.table_data.data.len().saturating_sub(1); - proc_widget_state.table_state.scroll_direction = ScrollDirection::Down; + proc_widget_state.table.set_last(); } } BottomWidgetType::ProcSort => { @@ -2048,9 +1895,7 @@ impl App { .proc_state .get_mut_widget_state(self.current_widget.widget_id - 2) { - proc_widget_state.sort_table_state.current_scroll_position = - proc_widget_state.num_enabled_columns() - 1; - proc_widget_state.sort_table_state.scroll_direction = ScrollDirection::Down; + proc_widget_state.sort_table.set_last(); } } BottomWidgetType::Temp => { @@ -2058,11 +1903,7 @@ impl App { .temp_state .get_mut_widget_state(self.current_widget.widget_id) { - if !self.converted_data.temp_sensor_data.data.is_empty() { - temp_widget_state.table_state.current_scroll_position = - self.converted_data.temp_sensor_data.data.len() - 1; - temp_widget_state.table_state.scroll_direction = ScrollDirection::Down; - } + temp_widget_state.table.set_last(); } } BottomWidgetType::Disk => { @@ -2070,10 +1911,8 @@ impl App { .disk_state .get_mut_widget_state(self.current_widget.widget_id) { - if !self.converted_data.disk_data.data.is_empty() { - disk_widget_state.table_state.current_scroll_position = - self.converted_data.disk_data.data.len() - 1; - disk_widget_state.table_state.scroll_direction = ScrollDirection::Down; + if !self.converted_data.disk_data.is_empty() { + disk_widget_state.table.set_last(); } } } @@ -2082,11 +1921,7 @@ impl App { .cpu_state .get_mut_widget_state(self.current_widget.widget_id - 1) { - let cap = self.converted_data.cpu_data.len(); - if cap > 0 { - cpu_widget_state.table_state.current_scroll_position = cap - 1; - cpu_widget_state.table_state.scroll_direction = ScrollDirection::Down; - } + cpu_widget_state.table.set_last(); } } _ => {} @@ -2128,10 +1963,9 @@ impl App { .proc_state .get_mut_widget_state(self.current_widget.widget_id - 2) { - let num_entries = proc_widget_state.num_enabled_columns(); proc_widget_state - .sort_table_state - .update_position(num_to_change_by, num_entries); + .sort_table + .increment_position(num_to_change_by); } } @@ -2141,9 +1975,7 @@ impl App { .widget_states .get_mut(&(self.current_widget.widget_id - 1)) { - cpu_widget_state - .table_state - .update_position(num_to_change_by, self.converted_data.cpu_data.len()); + cpu_widget_state.table.increment_position(num_to_change_by); } } @@ -2153,9 +1985,7 @@ impl App { .proc_state .get_mut_widget_state(self.current_widget.widget_id) { - proc_widget_state - .table_state - .update_position(num_to_change_by, proc_widget_state.table_data.data.len()) + proc_widget_state.table.increment_position(num_to_change_by) } else { None } @@ -2167,10 +1997,7 @@ impl App { .widget_states .get_mut(&self.current_widget.widget_id) { - temp_widget_state.table_state.update_position( - num_to_change_by, - self.converted_data.temp_sensor_data.data.len(), - ); + temp_widget_state.table.increment_position(num_to_change_by); } } @@ -2180,9 +2007,7 @@ impl App { .widget_states .get_mut(&self.current_widget.widget_id) { - disk_widget_state - .table_state - .update_position(num_to_change_by, self.converted_data.disk_data.data.len()); + disk_widget_state.table.increment_position(num_to_change_by); } } @@ -2267,7 +2092,7 @@ impl App { .widget_states .get_mut(&self.current_widget.widget_id) { - pws.toggle_tree_branch(); + pws.toggle_current_tree_branch_entry(); } } @@ -2645,31 +2470,22 @@ impl App { .get_widget_state(self.current_widget.widget_id) { if let Some(visual_index) = - proc_widget_state.table_state.table_state.selected() + proc_widget_state.table.tui_selected() { - // If in tree mode, also check to see if this click is on - // the same entry as the already selected one - if it is, - // then we minimize. - let is_tree_mode = matches!( proc_widget_state.mode, ProcWidgetMode::Tree { .. } ); + let change = + offset_clicked_entry as i64 - visual_index as i64; - let previous_scroll_position = proc_widget_state - .table_state - .current_scroll_position; + self.change_process_position(change); - let new_position = self.change_process_position( - offset_clicked_entry as i64 - visual_index as i64, - ); - - if is_tree_mode { - if let Some(new_position) = new_position { - if previous_scroll_position == new_position { - self.toggle_collapsing_process_branch(); - } - } + // If in tree mode, also check to see if this click is on + // the same entry as the already selected one - if it is, + // then we minimize. + if is_tree_mode && change == 0 { + self.toggle_collapsing_process_branch(); } } } @@ -2680,10 +2496,8 @@ impl App { .proc_state .get_widget_state(self.current_widget.widget_id - 2) { - if let Some(visual_index) = proc_widget_state - .sort_table_state - .table_state - .selected() + if let Some(visual_index) = + proc_widget_state.sort_table.tui_selected() { self.change_process_sort_position( offset_clicked_entry as i64 - visual_index as i64, @@ -2697,7 +2511,7 @@ impl App { .get_widget_state(self.current_widget.widget_id - 1) { if let Some(visual_index) = - cpu_widget_state.table_state.table_state.selected() + cpu_widget_state.table.tui_selected() { self.change_cpu_legend_position( offset_clicked_entry as i64 - visual_index as i64, @@ -2711,7 +2525,7 @@ impl App { .get_widget_state(self.current_widget.widget_id) { if let Some(visual_index) = - temp_widget_state.table_state.table_state.selected() + temp_widget_state.table.tui_selected() { self.change_temp_position( offset_clicked_entry as i64 - visual_index as i64, @@ -2725,7 +2539,7 @@ impl App { .get_widget_state(self.current_widget.widget_id) { if let Some(visual_index) = - disk_widget_state.table_state.table_state.selected() + disk_widget_state.table.tui_selected() { self.change_disk_position( offset_clicked_entry as i64 - visual_index as i64, @@ -2744,12 +2558,12 @@ impl App { .proc_state .get_mut_widget_state(self.current_widget.widget_id) { - if let SortState::Sortable(st) = - &mut proc_widget_state.table_state.sort_state + if proc_widget_state + .table + .try_select_location(x, y) + .is_some() { - if st.try_select_location(x, y).is_some() { - proc_widget_state.force_data_update(); - } + proc_widget_state.force_data_update(); } } } diff --git a/src/app/data_farmer.rs b/src/app/data_farmer.rs index a6670712..e409957f 100644 --- a/src/app/data_farmer.rs +++ b/src/app/data_farmer.rs @@ -33,7 +33,7 @@ use regex::Regex; pub type TimeOffset = f64; pub type Value = f64; -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct TimedData { pub rx_data: Value, pub tx_data: Value, @@ -45,19 +45,11 @@ pub struct TimedData { pub arc_data: Option, } -pub type StringPidMap = FxHashMap>; - #[derive(Clone, Debug, Default)] pub struct ProcessData { /// A PID to process data map. pub process_harvest: FxHashMap, - /// A mapping from a process name to any PID with that name. - pub name_pid_map: StringPidMap, - - /// A mapping from a process command to any PID with that name. - pub cmd_pid_map: StringPidMap, - /// A mapping between a process PID to any children process PIDs. pub process_parent_mapping: FxHashMap>, @@ -68,28 +60,10 @@ pub struct ProcessData { impl ProcessData { fn ingest(&mut self, list_of_processes: Vec) { // TODO: [Optimization] Probably more efficient to all of this in the data collection step, but it's fine for now. - self.name_pid_map.clear(); - self.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.name_pid_map.get_mut(&process_harvest.name) { - entry.push(process_harvest.pid); - } else { - self.name_pid_map - .insert(process_harvest.name.to_string(), vec![process_harvest.pid]); - } - - if let Some(entry) = self.cmd_pid_map.get_mut(&process_harvest.command) { - entry.push(process_harvest.pid); - } else { - self.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); @@ -100,8 +74,6 @@ impl ProcessData { } }); - self.name_pid_map.shrink_to_fit(); - self.cmd_pid_map.shrink_to_fit(); self.process_parent_mapping.shrink_to_fit(); let process_pid_map = list_of_processes @@ -137,14 +109,14 @@ impl ProcessData { /// collected, and what is needed to convert into a displayable form. /// /// If the app is *frozen* - that is, we do not want to *display* any changing -/// data, keep updating this, don't convert to canvas displayable data! +/// data, keep updating this. As of 2021-09-08, we just clone the current collection +/// when it freezes to have a snapshot floating around. /// /// Note that with this method, the *app* thread is responsible for cleaning - /// not the data collector. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct DataCollection { pub current_instant: Instant, - pub frozen_instant: Option, pub timed_data_vec: Vec<(Instant, TimedData)>, pub network_harvest: network::NetworkHarvest, pub memory_harvest: memory::MemHarvest, @@ -167,7 +139,6 @@ impl Default for DataCollection { fn default() -> Self { DataCollection { current_instant: Instant::now(), - frozen_instant: None, timed_data_vec: Vec::default(), network_harvest: network::NetworkHarvest::default(), memory_harvest: memory::MemHarvest::default(), @@ -210,14 +181,6 @@ impl DataCollection { } } - pub fn freeze(&mut self) { - self.frozen_instant = Some(self.current_instant); - } - - pub fn thaw(&mut self) { - self.frozen_instant = None; - } - pub fn clean_data(&mut self, max_time_millis: u64) { let current_time = Instant::now(); diff --git a/src/app/data_harvester/cpu.rs b/src/app/data_harvester/cpu.rs index 23073606..c4131876 100644 --- a/src/app/data_harvester/cpu.rs +++ b/src/app/data_harvester/cpu.rs @@ -16,10 +16,15 @@ cfg_if::cfg_if! { pub type LoadAvgHarvest = [f32; 3]; -#[derive(Default, Debug, Clone)] +#[derive(Debug, Clone, Copy)] +pub enum CpuDataType { + Avg, + Cpu(usize), +} + +#[derive(Debug, Clone)] pub struct CpuData { - pub cpu_prefix: String, - pub cpu_count: Option, + pub data_type: CpuDataType, pub cpu_usage: f64, } diff --git a/src/app/data_harvester/cpu/heim.rs b/src/app/data_harvester/cpu/heim.rs index be2b251f..cdb6c8c2 100644 --- a/src/app/data_harvester/cpu/heim.rs +++ b/src/app/data_harvester/cpu/heim.rs @@ -18,7 +18,8 @@ cfg_if::cfg_if! { } } -use crate::data_harvester::cpu::{CpuData, CpuHarvest, PastCpuTotal, PastCpuWork}; +use crate::data_harvester::cpu::{CpuData, CpuDataType, CpuHarvest, PastCpuTotal, PastCpuWork}; + use futures::StreamExt; use std::collections::VecDeque; @@ -62,8 +63,7 @@ pub async fn get_cpu_data_list( let present_times = convert_cpu_times(&present); new_cpu_times.push(present_times); cpu_deque.push_back(CpuData { - cpu_prefix: "CPU".to_string(), - cpu_count: Some(itx), + data_type: CpuDataType::Cpu(itx), cpu_usage: calculate_cpu_usage_percentage( convert_cpu_times(&past), present_times, @@ -72,8 +72,7 @@ pub async fn get_cpu_data_list( } else { new_cpu_times.push((0.0, 0.0)); cpu_deque.push_back(CpuData { - cpu_prefix: "CPU".to_string(), - cpu_count: Some(itx), + data_type: CpuDataType::Cpu(itx), cpu_usage: 0.0, }); } @@ -96,8 +95,7 @@ pub async fn get_cpu_data_list( ( present_times, CpuData { - cpu_prefix: "CPU".to_string(), - cpu_count: Some(itx), + data_type: CpuDataType::Cpu(itx), cpu_usage: calculate_cpu_usage_percentage( (*past_cpu_work, *past_cpu_total), present_times, @@ -108,8 +106,7 @@ pub async fn get_cpu_data_list( ( (*past_cpu_work, *past_cpu_total), CpuData { - cpu_prefix: "CPU".to_string(), - cpu_count: Some(itx), + data_type: CpuDataType::Cpu(itx), cpu_usage: 0.0, }, ) @@ -147,8 +144,7 @@ pub async fn get_cpu_data_list( *previous_average_cpu_time = Some(new_average_cpu_time); cpu_deque.push_front(CpuData { - cpu_prefix: "AVG".to_string(), - cpu_count: None, + data_type: CpuDataType::Avg, cpu_usage, }) } diff --git a/src/app/data_harvester/processes/linux.rs b/src/app/data_harvester/processes/linux.rs index 99412247..a53a3e9a 100644 --- a/src/app/data_harvester/processes/linux.rs +++ b/src/app/data_harvester/processes/linux.rs @@ -116,7 +116,6 @@ fn get_linux_cpu_usage( } } -#[allow(clippy::too_many_arguments)] fn read_proc( prev_proc: &PrevProcDetails, stat: &Stat, cpu_usage: f64, cpu_fraction: f64, use_current_cpu_total: bool, time_difference_in_secs: u64, mem_total_kb: u64, diff --git a/src/app/data_harvester/processes/macos/sysctl_bindings.rs b/src/app/data_harvester/processes/macos/sysctl_bindings.rs index 77b8901b..22bc07c6 100644 --- a/src/app/data_harvester/processes/macos/sysctl_bindings.rs +++ b/src/app/data_harvester/processes/macos/sysctl_bindings.rs @@ -60,16 +60,16 @@ pub(crate) struct extern_proc { /// Process identifier. pub p_pid: pid_t, - /// Save parent pid during ptrace. XXX + /// Save parent pid during ptrace. pub p_oppid: pid_t, - /// Sideways return value from fdopen. XXX + /// Sideways return value from fdopen. pub p_dupfd: i32, /// where user stack was allocated pub user_stack: caddr_t, - /// XXX Which thread is exiting? + /// Which thread is exiting? pub exit_thread: *mut c_void, /// allow to debug diff --git a/src/app/data_harvester/temperature.rs b/src/app/data_harvester/temperature.rs index 23465588..a9855f51 100644 --- a/src/app/data_harvester/temperature.rs +++ b/src/app/data_harvester/temperature.rs @@ -26,7 +26,7 @@ pub struct TempHarvest { pub temperature: f32, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Copy)] pub enum TemperatureType { Celsius, Kelvin, diff --git a/src/app/frozen_state.rs b/src/app/frozen_state.rs new file mode 100644 index 00000000..4fc7293d --- /dev/null +++ b/src/app/frozen_state.rs @@ -0,0 +1,44 @@ +use super::DataCollection; + +/// The [`FrozenState`] indicates whether the application state should be frozen. It is either not frozen or +/// frozen and containing a copy of the state at the time. +pub enum FrozenState { + NotFrozen, + Frozen(Box), +} + +impl Default for FrozenState { + fn default() -> Self { + Self::NotFrozen + } +} + +pub type IsFrozen = bool; + +impl FrozenState { + /// Checks whether the [`FrozenState`] is currently frozen. + pub fn is_frozen(&self) -> IsFrozen { + matches!(self, FrozenState::Frozen(_)) + } + + /// Freezes the [`FrozenState`]. + pub fn freeze(&mut self, data: Box) { + *self = FrozenState::Frozen(data); + } + + /// Unfreezes the [`FrozenState`]. + pub fn thaw(&mut self) { + *self = FrozenState::NotFrozen; + } + + /// Toggles the [`FrozenState`] and returns whether it is now frozen. + pub fn toggle(&mut self, data: &DataCollection) -> IsFrozen { + if self.is_frozen() { + self.thaw(); + false + } else { + self.freeze(Box::new(data.clone())); + true + } + } +} diff --git a/src/app/states.rs b/src/app/states.rs index 2ca18c0b..a1465640 100644 --- a/src/app/states.rs +++ b/src/app/states.rs @@ -4,11 +4,10 @@ use unicode_segmentation::GraphemeCursor; use crate::{ app::{layout_manager::BottomWidgetType, query::*}, - components::text_table::{CellContent, TableComponentColumn, TableComponentState, WidthBounds}, constants, }; -use super::widgets::{DiskWidgetState, ProcWidget, TempWidgetState}; +use super::widgets::{CpuWidgetState, DiskTableWidget, ProcWidget, TempWidgetState}; #[derive(Debug)] pub enum ScrollDirection { @@ -30,13 +29,6 @@ pub enum CursorDirection { Right, } -/// Meant for canvas operations involving table column widths. -#[derive(Default)] -pub struct CanvasTableWidthState { - pub desired_column_widths: Vec, - pub calculated_column_widths: Vec, -} - #[derive(PartialEq, Eq)] pub enum KillSignal { Cancel, @@ -184,42 +176,6 @@ impl NetState { } } -pub struct CpuWidgetState { - pub current_display_time: u64, - pub is_legend_hidden: bool, - pub autohide_timer: Option, - pub table_state: TableComponentState, - pub is_multi_graph_mode: bool, -} - -impl CpuWidgetState { - pub fn init(current_display_time: u64, autohide_timer: Option) -> Self { - const CPU_LEGEND_HEADER: [&str; 2] = ["CPU", "Use%"]; - const WIDTHS: [WidthBounds; CPU_LEGEND_HEADER.len()] = [ - WidthBounds::soft_from_str("CPU", Some(0.5)), - WidthBounds::soft_from_str("Use%", Some(0.5)), - ]; - - let table_state = TableComponentState::new( - CPU_LEGEND_HEADER - .iter() - .zip(WIDTHS) - .map(|(c, width)| { - TableComponentColumn::new_custom(CellContent::new(*c, None), width) - }) - .collect(), - ); - - CpuWidgetState { - current_display_time, - is_legend_hidden: false, - autohide_timer, - table_state, - is_multi_graph_mode: false, - } - } -} - pub struct CpuState { pub force_update: Option, pub widget_states: HashMap, @@ -296,19 +252,19 @@ impl TempState { } pub struct DiskState { - pub widget_states: HashMap, + pub widget_states: HashMap, } impl DiskState { - pub fn init(widget_states: HashMap) -> Self { + pub fn init(widget_states: HashMap) -> Self { DiskState { widget_states } } - pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut DiskWidgetState> { + pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut DiskTableWidget> { self.widget_states.get_mut(&widget_id) } - pub fn get_widget_state(&self, widget_id: u64) -> Option<&DiskWidgetState> { + pub fn get_widget_state(&self, widget_id: u64) -> Option<&DiskTableWidget> { self.widget_states.get(&widget_id) } } diff --git a/src/app/widgets.rs b/src/app/widgets.rs index 99e9e210..6bdb0e55 100644 --- a/src/app/widgets.rs +++ b/src/app/widgets.rs @@ -1,8 +1,13 @@ -pub mod process_table_widget; -pub use process_table_widget::*; +// FIXME: Move this outside of app, along with components? -pub mod temperature_table_widget; -pub use temperature_table_widget::*; +pub mod process_table; +pub use process_table::*; -pub mod disk_table_widget; -pub use disk_table_widget::*; +pub mod temperature_table; +pub use temperature_table::*; + +pub mod disk_table; +pub use disk_table::*; + +pub mod cpu_graph; +pub use cpu_graph::*; diff --git a/src/app/widgets/cpu_graph.rs b/src/app/widgets/cpu_graph.rs new file mode 100644 index 00000000..4c127bb4 --- /dev/null +++ b/src/app/widgets/cpu_graph.rs @@ -0,0 +1,175 @@ +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