diff --git a/src/app.rs b/src/app.rs index ec577661..4b58b014 100644 --- a/src/app.rs +++ b/src/app.rs @@ -7,24 +7,20 @@ mod process_killer; pub mod query; pub mod widgets; -use std::{collections::HashMap, time::Instant}; +use std::time::Instant; use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseEvent}; use fxhash::FxHashMap; use indextree::{Arena, NodeId}; -use unicode_width::UnicodeWidthStr; pub use data_farmer::*; -use data_harvester::{processes, temperature}; +use data_harvester::temperature; pub use filter::*; use layout_manager::*; pub use widgets::*; use crate::{ - canvas, constants, - units::data_units::DataUnit, - utils::error::{BottomError, Result}, - BottomEvent, Pid, + canvas, constants, units::data_units::DataUnit, utils::error::Result, BottomEvent, Pid, }; use self::event::{ComponentEventResult, EventResult, ReturnSignal}; @@ -91,7 +87,7 @@ pub struct AppConfigFields { pub hide_time: bool, pub autohide_time: bool, pub use_old_network_legend: bool, - pub table_gap: u16, // TODO: Just make this a bool... + pub table_gap: u16, // TODO: [Config, Refactor] Just make this a bool... pub disable_click: bool, pub no_write: bool, pub show_table_scroll_position: bool, @@ -129,23 +125,8 @@ pub struct AppState { pub filters: DataFilters, pub app_config_fields: AppConfigFields, - // --- Eventually delete/rewrite --- + // --- FIXME: TO DELETE/REWRITE --- pub delete_dialog_state: AppDeleteDialogState, - - // --- TO DELETE --- - pub cpu_state: CpuState, - pub mem_state: MemState, - pub net_state: NetState, - pub proc_state: ProcState, - pub temp_state: TempState, - pub disk_state: DiskState, - pub battery_state: BatteryState, - pub basic_table_widget_state: Option, - pub widget_map: HashMap, - pub current_widget: BottomWidget, - - pub basic_mode_use_percent: bool, - pub is_force_redraw: bool, pub is_determining_widget_boundary: bool, @@ -190,17 +171,6 @@ impl AppState { data_collection: Default::default(), is_expanded: Default::default(), delete_dialog_state: Default::default(), - cpu_state: Default::default(), - mem_state: Default::default(), - net_state: Default::default(), - proc_state: Default::default(), - temp_state: Default::default(), - disk_state: Default::default(), - battery_state: Default::default(), - basic_table_widget_state: Default::default(), - widget_map: Default::default(), - current_widget: Default::default(), - basic_mode_use_percent: Default::default(), is_force_redraw: Default::default(), is_determining_widget_boundary: Default::default(), frozen_state: Default::default(), @@ -484,7 +454,7 @@ impl AppState { BottomEvent::Update(new_data) => { self.data_collection.eat_data(new_data); - // TODO: Optimization for dialogs; don't redraw here. + // TODO: [Optimization] Optimization for dialogs - don't redraw on an update! if !self.is_frozen() { let data_collection = &self.data_collection; @@ -509,104 +479,6 @@ impl AppState { } } - pub fn is_in_search_widget(&self) -> bool { - matches!( - self.current_widget.widget_type, - BottomWidgetType::ProcSearch - ) - } - - fn is_in_dialog(&self) -> bool { - self.delete_dialog_state.is_showing_dd - } - - fn ignore_normal_keybinds(&self) -> bool { - self.is_in_dialog() - } - - pub fn on_tab(&mut self) { - // 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; - } - } - BottomWidgetType::Proc => { - if let Some(proc_widget_state) = self - .proc_state - .get_mut_widget_state(self.current_widget.widget_id) - { - // Do NOT allow when in tree mode! - if !proc_widget_state.is_tree_mode { - // Toggles process widget grouping state - proc_widget_state.is_grouped = !(proc_widget_state.is_grouped); - - // Forcefully switch off column if we were on it... - if (proc_widget_state.is_grouped - && (proc_widget_state.process_sorting_type - == processes::ProcessSorting::Pid - || proc_widget_state.process_sorting_type - == processes::ProcessSorting::User - || proc_widget_state.process_sorting_type - == processes::ProcessSorting::State)) - || (!proc_widget_state.is_grouped - && proc_widget_state.process_sorting_type - == processes::ProcessSorting::Count) - { - proc_widget_state.process_sorting_type = - processes::ProcessSorting::CpuPercent; // Go back to default, negate PID for group - proc_widget_state.is_process_sort_descending = true; - } - - proc_widget_state.columns.set_to_sorted_index_from_type( - &proc_widget_state.process_sorting_type, - ); - - proc_widget_state.columns.try_set( - &processes::ProcessSorting::State, - !(proc_widget_state.is_grouped), - ); - - #[cfg(target_family = "unix")] - proc_widget_state.columns.try_set( - &processes::ProcessSorting::User, - !(proc_widget_state.is_grouped), - ); - - proc_widget_state - .columns - .toggle(&processes::ProcessSorting::Count); - proc_widget_state - .columns - .toggle(&processes::ProcessSorting::Pid); - - proc_widget_state.requires_redraw = true; - self.proc_state.force_update = Some(self.current_widget.widget_id); - } - } - } - _ => {} - } - } - } - - /// I don't like this, but removing it causes a bunch of breakage. - /// Use ``proc_widget_state.is_grouped`` if possible! - pub fn is_grouped(&self, widget_id: u64) -> bool { - if let Some(proc_widget_state) = self.proc_state.widget_states.get(&widget_id) { - proc_widget_state.is_grouped - } else { - false - } - } - #[cfg(target_family = "unix")] pub fn on_number(&mut self, number_char: char) { if self.delete_dialog_state.is_showing_dd { @@ -642,149 +514,122 @@ impl AppState { } pub fn on_left_key(&mut self) { - if !self.is_in_dialog() { - match self.current_widget.widget_type { - BottomWidgetType::ProcSearch => { - let is_in_search_widget = self.is_in_search_widget(); - if let Some(proc_widget_state) = self - .proc_state - .get_mut_widget_state(self.current_widget.widget_id - 1) - { - if is_in_search_widget { - let prev_cursor = proc_widget_state.get_search_cursor_position(); - proc_widget_state - .search_walk_back(proc_widget_state.get_search_cursor_position()); - if proc_widget_state.get_search_cursor_position() < prev_cursor { - let str_slice = &proc_widget_state - .process_search_state - .search_state - .current_search_query - [proc_widget_state.get_search_cursor_position()..prev_cursor]; - proc_widget_state - .process_search_state - .search_state - .char_cursor_position -= UnicodeWidthStr::width(str_slice); - proc_widget_state - .process_search_state - .search_state - .cursor_direction = CursorDirection::Left; - } - } - } - } - BottomWidgetType::Battery => { - if !self.canvas_data.battery_data.is_empty() { - if let Some(battery_widget_state) = self - .battery_state - .get_mut_widget_state(self.current_widget.widget_id) - { - if battery_widget_state.currently_selected_battery_index > 0 { - battery_widget_state.currently_selected_battery_index -= 1; - } - } - } - } - _ => {} - } - } else if self.delete_dialog_state.is_showing_dd { - #[cfg(target_family = "unix")] - { - if self.app_config_fields.is_advanced_kill { - match self.delete_dialog_state.selected_signal { - KillSignal::Kill(prev_signal) => { - self.delete_dialog_state.selected_signal = match prev_signal - 1 { - 0 => KillSignal::Cancel, - // 32+33 are skipped - 33 => KillSignal::Kill(31), - signal => KillSignal::Kill(signal), - }; - } - KillSignal::Cancel => {} - }; - } else { - self.delete_dialog_state.selected_signal = KillSignal::default(); - } - } - #[cfg(target_os = "windows")] - { - self.delete_dialog_state.selected_signal = KillSignal::Kill(1); - } - } + // if !self.is_in_dialog() { + // match self.current_widget.widget_type { + // BottomWidgetType::ProcSearch => { + // let is_in_search_widget = self.is_in_search_widget(); + // if let Some(proc_widget_state) = self + // .proc_state + // .get_mut_widget_state(self.current_widget.widget_id - 1) + // { + // if is_in_search_widget { + // let prev_cursor = proc_widget_state.get_search_cursor_position(); + // proc_widget_state + // .search_walk_back(proc_widget_state.get_search_cursor_position()); + // if proc_widget_state.get_search_cursor_position() < prev_cursor { + // let str_slice = &proc_widget_state + // .process_search_state + // .search_state + // .current_search_query + // [proc_widget_state.get_search_cursor_position()..prev_cursor]; + // proc_widget_state + // .process_search_state + // .search_state + // .char_cursor_position -= UnicodeWidthStr::width(str_slice); + // proc_widget_state + // .process_search_state + // .search_state + // .cursor_direction = CursorDirection::Left; + // } + // } + // } + // } + // _ => {} + // } + // } else if self.delete_dialog_state.is_showing_dd { + // #[cfg(target_family = "unix")] + // { + // if self.app_config_fields.is_advanced_kill { + // match self.delete_dialog_state.selected_signal { + // KillSignal::Kill(prev_signal) => { + // self.delete_dialog_state.selected_signal = match prev_signal - 1 { + // 0 => KillSignal::Cancel, + // // 32+33 are skipped + // 33 => KillSignal::Kill(31), + // signal => KillSignal::Kill(signal), + // }; + // } + // KillSignal::Cancel => {} + // }; + // } else { + // self.delete_dialog_state.selected_signal = KillSignal::default(); + // } + // } + // #[cfg(target_os = "windows")] + // { + // self.delete_dialog_state.selected_signal = KillSignal::Kill(1); + // } + // } } pub fn on_right_key(&mut self) { - if !self.is_in_dialog() { - match self.current_widget.widget_type { - BottomWidgetType::ProcSearch => { - let is_in_search_widget = self.is_in_search_widget(); - if let Some(proc_widget_state) = self - .proc_state - .get_mut_widget_state(self.current_widget.widget_id - 1) - { - if is_in_search_widget { - let prev_cursor = proc_widget_state.get_search_cursor_position(); - proc_widget_state.search_walk_forward( - proc_widget_state.get_search_cursor_position(), - ); - if proc_widget_state.get_search_cursor_position() > prev_cursor { - let str_slice = &proc_widget_state - .process_search_state - .search_state - .current_search_query - [prev_cursor..proc_widget_state.get_search_cursor_position()]; - proc_widget_state - .process_search_state - .search_state - .char_cursor_position += UnicodeWidthStr::width(str_slice); - proc_widget_state - .process_search_state - .search_state - .cursor_direction = CursorDirection::Right; - } - } - } - } - BottomWidgetType::Battery => { - if !self.canvas_data.battery_data.is_empty() { - let battery_count = self.canvas_data.battery_data.len(); - if let Some(battery_widget_state) = self - .battery_state - .get_mut_widget_state(self.current_widget.widget_id) - { - if battery_widget_state.currently_selected_battery_index - < battery_count - 1 - { - battery_widget_state.currently_selected_battery_index += 1; - } - } - } - } - _ => {} - } - } else if self.delete_dialog_state.is_showing_dd { - #[cfg(target_family = "unix")] - { - if self.app_config_fields.is_advanced_kill { - let new_signal = match self.delete_dialog_state.selected_signal { - KillSignal::Cancel => 1, - // 32+33 are skipped - #[cfg(target_os = "linux")] - KillSignal::Kill(31) => 34, - #[cfg(target_os = "macos")] - KillSignal::Kill(31) => 31, - KillSignal::Kill(64) => 64, - KillSignal::Kill(signal) => signal + 1, - }; - self.delete_dialog_state.selected_signal = KillSignal::Kill(new_signal); - } else { - self.delete_dialog_state.selected_signal = KillSignal::Cancel; - } - } - #[cfg(target_os = "windows")] - { - self.delete_dialog_state.selected_signal = KillSignal::Cancel; - } - } + // if !self.is_in_dialog() { + // match self.current_widget.widget_type { + // BottomWidgetType::ProcSearch => { + // let is_in_search_widget = self.is_in_search_widget(); + // if let Some(proc_widget_state) = self + // .proc_state + // .get_mut_widget_state(self.current_widget.widget_id - 1) + // { + // if is_in_search_widget { + // let prev_cursor = proc_widget_state.get_search_cursor_position(); + // proc_widget_state.search_walk_forward( + // proc_widget_state.get_search_cursor_position(), + // ); + // if proc_widget_state.get_search_cursor_position() > prev_cursor { + // let str_slice = &proc_widget_state + // .process_search_state + // .search_state + // .current_search_query + // [prev_cursor..proc_widget_state.get_search_cursor_position()]; + // proc_widget_state + // .process_search_state + // .search_state + // .char_cursor_position += UnicodeWidthStr::width(str_slice); + // proc_widget_state + // .process_search_state + // .search_state + // .cursor_direction = CursorDirection::Right; + // } + // } + // } + // } + // _ => {} + // } + // } else if self.delete_dialog_state.is_showing_dd { + // #[cfg(target_family = "unix")] + // { + // if self.app_config_fields.is_advanced_kill { + // let new_signal = match self.delete_dialog_state.selected_signal { + // KillSignal::Cancel => 1, + // // 32+33 are skipped + // #[cfg(target_os = "linux")] + // KillSignal::Kill(31) => 34, + // #[cfg(target_os = "macos")] + // KillSignal::Kill(31) => 31, + // KillSignal::Kill(64) => 64, + // KillSignal::Kill(signal) => signal + 1, + // }; + // self.delete_dialog_state.selected_signal = KillSignal::Kill(new_signal); + // } else { + // self.delete_dialog_state.selected_signal = KillSignal::Cancel; + // } + // } + // #[cfg(target_os = "windows")] + // { + // self.delete_dialog_state.selected_signal = KillSignal::Cancel; + // } + // } } pub fn start_killing_process(&mut self) { @@ -828,35 +673,38 @@ impl AppState { } pub 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")] - let signal = match self.delete_dialog_state.selected_signal { - KillSignal::Kill(sig) => sig, - KillSignal::Cancel => 15, // should never happen, so just TERM - }; - for pid in ¤t_selected_processes.1 { - #[cfg(target_family = "unix")] - { - process_killer::kill_process_given_pid(*pid, signal)?; - } - #[cfg(target_os = "windows")] - { - process_killer::kill_process_given_pid(*pid)?; - } - } - } - self.to_delete_process_list = None; - Ok(()) - } else { - Err(BottomError::GenericError( - "Cannot kill processes if the current widget is not the Process widget!" - .to_string(), - )) - } + // if let BottomWidgetType::Proc = self.current_widget.widget_type { + // if let Some(current_selected_processes) = &self.to_delete_process_list { + // #[cfg(target_family = "unix")] + // let signal = match self.delete_dialog_state.selected_signal { + // KillSignal::Kill(sig) => sig, + // KillSignal::Cancel => 15, // should never happen, so just TERM + // }; + // for pid in ¤t_selected_processes.1 { + // #[cfg(target_family = "unix")] + // { + // process_killer::kill_process_given_pid(*pid, signal)?; + // } + // #[cfg(target_os = "windows")] + // { + // process_killer::kill_process_given_pid(*pid)?; + // } + // } + // } + // self.to_delete_process_list = None; + // Ok(()) + // } else { + // Err(BottomError::GenericError( + // "Cannot kill processes if the current widget is not the Process widget!" + // .to_string(), + // )) + // } + + Ok(()) } pub fn get_to_delete_processes(&self) -> Option<(String, Vec)> { - self.to_delete_process_list.clone() + // self.to_delete_process_list.clone() + todo!() } } diff --git a/src/app/data_farmer.rs b/src/app/data_farmer.rs index 7badfe57..6b850f8b 100644 --- a/src/app/data_farmer.rs +++ b/src/app/data_farmer.rs @@ -319,7 +319,7 @@ impl DataCollection { } fn eat_proc(&mut self, list_of_processes: Vec) { - // TODO: Probably more efficient to do this in the data collection step, but it's fine for now. + // 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| { diff --git a/src/app/data_harvester.rs b/src/app/data_harvester.rs index 2f5613cc..2672022d 100644 --- a/src/app/data_harvester.rs +++ b/src/app/data_harvester.rs @@ -149,7 +149,7 @@ impl DataCollector { self.sys.refresh_memory(); self.mem_total_kb = self.sys.get_total_memory(); - // TODO: Would be good to get this and network list running on a timer instead...? + // TODO: [Data Collection] Would be good to get this and network list running on a timer instead...? // Refresh components list once... if self.widgets_to_harvest.use_temp { self.sys.refresh_components_list(); diff --git a/src/app/data_harvester/network/heim.rs b/src/app/data_harvester/network/heim.rs index 3c12fd73..6d05cc60 100644 --- a/src/app/data_harvester/network/heim.rs +++ b/src/app/data_harvester/network/heim.rs @@ -39,7 +39,7 @@ pub async fn get_network_data( }; if to_keep { - // TODO: Use bytes as the default instead, perhaps? + // TODO: [Optimization] Optimization (Potential)Use bytes as the default instead, perhaps? // Since you might have to do a double conversion (bytes -> bits -> bytes) in some cases; // but if you stick to bytes, then in the bytes, case, you do no conversion, and in the bits case, // you only do one conversion... diff --git a/src/app/data_harvester/processes.rs b/src/app/data_harvester/processes.rs index 151d3a55..4b09a078 100644 --- a/src/app/data_harvester/processes.rs +++ b/src/app/data_harvester/processes.rs @@ -27,6 +27,7 @@ use std::borrow::Cow; use crate::Pid; +// FIXME: [URGENT] Delete this. #[derive(Clone, PartialEq, Eq, Hash, Debug)] pub enum ProcessSorting { CpuPercent, diff --git a/src/app/data_harvester/processes/linux.rs b/src/app/data_harvester/processes/linux.rs index c1b7f4e8..aad67adf 100644 --- a/src/app/data_harvester/processes/linux.rs +++ b/src/app/data_harvester/processes/linux.rs @@ -229,8 +229,6 @@ pub fn get_process_data( pid_mapping: &mut FxHashMap, use_current_cpu_total: bool, time_difference_in_secs: u64, mem_total_kb: u64, user_table: &mut UserTable, ) -> crate::utils::error::Result> { - // TODO: [PROC THREADS] Add threads - if let Ok((cpu_usage, cpu_fraction)) = cpu_usage_calculation(prev_idle, prev_non_idle) { let mut pids_to_clear: FxHashSet = pid_mapping.keys().cloned().collect(); diff --git a/src/app/layout_manager.rs b/src/app/layout_manager.rs index 48e97774..bde3febb 100644 --- a/src/app/layout_manager.rs +++ b/src/app/layout_manager.rs @@ -332,7 +332,7 @@ pub struct LayoutCreationOutput { /// Creates a new [`Arena`] from the given config and returns it, along with the [`NodeId`] representing /// the root of the newly created [`Arena`], a mapping from [`NodeId`]s to [`BottomWidget`]s, and optionally, a default /// selected [`NodeId`]. -// FIXME: This is currently jury-rigged "glue" just to work with the existing config system! We are NOT keeping it like this, it's too awful to keep like this! +// FIXME: [AFTER REFACTOR] This is currently jury-rigged "glue" just to work with the existing config system! We are NOT keeping it like this, it's too awful to keep like this! pub fn create_layout_tree( rows: &[Row], process_defaults: ProcessDefaults, app_config_fields: &AppConfigFields, ) -> Result { @@ -924,6 +924,7 @@ pub fn move_widget_selection( if let Some(proposed_widget) = widget_lookup_map.get_mut(&proposed_id) { match proposed_widget.selectable_type() { SelectableType::Unselectable => { + // FIXME: [URGENT] Test this; make sure this cannot recurse infinitely! Maybe through a unit test too. // Try to move again recursively. move_widget_selection( layout_tree, @@ -960,7 +961,7 @@ pub fn generate_layout( root: NodeId, arena: &mut Arena, area: Rect, lookup_map: &FxHashMap, ) { - // TODO: [Layout] Add some caching/dirty mechanisms to reduce calls. + // TODO: [Optimization, Layout] Add some caching/dirty mechanisms to reduce calls. /// A [`Size`] is a set of widths and heights that a node in our layout wants to be. #[derive(Default, Clone, Copy, Debug)] diff --git a/src/app/query.rs b/src/app/query.rs index b7522485..b32a23df 100644 --- a/src/app/query.rs +++ b/src/app/query.rs @@ -253,7 +253,7 @@ pub fn parse_query( if content == "=" { // Check next string if possible if let Some(queue_next) = query.pop_front() { - // TODO: Need to consider the following cases: + // TODO: [Query, ???] Need to consider the following cases: // - (test) // - (test // - test) diff --git a/src/app/states.rs b/src/app/states.rs deleted file mode 100644 index bf0b87af..00000000 --- a/src/app/states.rs +++ /dev/null @@ -1,940 +0,0 @@ -use std::{collections::HashMap, time::Instant}; - -use unicode_segmentation::GraphemeCursor; - -use tui::widgets::TableState; - -use crate::{ - app::{layout_manager::BottomWidgetType, query::*}, - constants, - data_harvester::processes::{self, ProcessSorting}, -}; -use ProcessSorting::*; - -#[derive(Debug)] -pub enum ScrollDirection { - // UP means scrolling up --- this usually DECREMENTS - Up, - // DOWN means scrolling down --- this usually INCREMENTS - Down, -} - -impl Default for ScrollDirection { - fn default() -> Self { - ScrollDirection::Down - } -} - -#[derive(Debug)] -pub enum CursorDirection { - Left, - Right, -} - -/// AppScrollWidgetState deals with fields for a scrollable app's current state. -#[derive(Default)] -pub struct AppScrollWidgetState { - pub current_scroll_position: usize, - pub previous_scroll_position: usize, - pub scroll_direction: ScrollDirection, - pub table_state: TableState, -} - -#[derive(PartialEq)] -pub enum KillSignal { - Cancel, - Kill(usize), -} - -impl Default for KillSignal { - #[cfg(target_family = "unix")] - fn default() -> Self { - KillSignal::Kill(15) - } - #[cfg(target_os = "windows")] - fn default() -> Self { - KillSignal::Kill(1) - } -} - -#[derive(Default)] -pub struct AppDeleteDialogState { - pub is_showing_dd: bool, - pub selected_signal: KillSignal, - /// tl x, tl y, br x, br y, index/signal - pub button_positions: Vec<(u16, u16, u16, u16, usize)>, - pub keyboard_signal_select: usize, - pub last_number_press: Option, - pub scroll_pos: usize, -} - -pub struct AppHelpDialogState { - pub is_showing_help: bool, - pub scroll_state: ParagraphScrollState, - pub index_shortcuts: Vec, -} - -impl Default for AppHelpDialogState { - fn default() -> Self { - AppHelpDialogState { - is_showing_help: false, - scroll_state: ParagraphScrollState::default(), - index_shortcuts: vec![0; constants::HELP_TEXT.len()], - } - } -} - -/// AppSearchState deals with generic searching (I might do this in the future). -pub struct AppSearchState { - pub is_enabled: bool, - pub current_search_query: String, - pub is_blank_search: bool, - pub is_invalid_search: bool, - pub grapheme_cursor: GraphemeCursor, - pub cursor_direction: CursorDirection, - pub cursor_bar: usize, - /// This represents the position in terms of CHARACTERS, not graphemes - pub char_cursor_position: usize, - /// The query - pub query: Option, - pub error_message: Option, -} - -impl Default for AppSearchState { - fn default() -> Self { - AppSearchState { - is_enabled: false, - current_search_query: String::default(), - is_invalid_search: false, - is_blank_search: true, - grapheme_cursor: GraphemeCursor::new(0, 0, true), - cursor_direction: CursorDirection::Right, - cursor_bar: 0, - char_cursor_position: 0, - query: None, - error_message: None, - } - } -} - -impl AppSearchState { - /// Returns a reset but still enabled app search state - pub fn reset(&mut self) { - *self = AppSearchState { - is_enabled: self.is_enabled, - ..AppSearchState::default() - } - } - - pub fn is_invalid_or_blank_search(&self) -> bool { - self.is_blank_search || self.is_invalid_search - } -} - -/// Meant for canvas operations involving table column widths. -#[derive(Default)] -pub struct CanvasTableWidthState { - pub desired_column_widths: Vec, - pub calculated_column_widths: Vec, -} - -/// 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; - } -} - -pub struct ColumnInfo { - pub enabled: bool, - pub shortcut: Option<&'static str>, - // FIXME: Move column width logic here! - // pub hard_width: Option, - // pub max_soft_width: Option, -} - -pub struct ProcColumn { - pub ordered_columns: Vec, - /// The y location of headers. Since they're all aligned, it's just one value. - pub column_header_y_loc: Option, - /// The x start and end bounds for each header. - pub column_header_x_locs: Option>, - pub column_mapping: HashMap, - pub longest_header_len: u16, - pub column_state: TableState, - pub scroll_direction: ScrollDirection, - pub current_scroll_position: usize, - pub previous_scroll_position: usize, - pub backup_prev_scroll_position: usize, -} - -impl Default for ProcColumn { - fn default() -> Self { - let ordered_columns = vec![ - Count, - Pid, - ProcessName, - Command, - CpuPercent, - Mem, - MemPercent, - ReadPerSecond, - WritePerSecond, - TotalRead, - TotalWrite, - User, - State, - ]; - - let mut column_mapping = HashMap::new(); - let mut longest_header_len = 0; - for column in ordered_columns.clone() { - longest_header_len = std::cmp::max(longest_header_len, column.to_string().len()); - match column { - CpuPercent => { - column_mapping.insert( - column, - ColumnInfo { - enabled: true, - shortcut: Some("c"), - // hard_width: None, - // max_soft_width: None, - }, - ); - } - MemPercent => { - column_mapping.insert( - column, - ColumnInfo { - enabled: true, - shortcut: Some("m"), - // hard_width: None, - // max_soft_width: None, - }, - ); - } - Mem => { - column_mapping.insert( - column, - ColumnInfo { - enabled: false, - shortcut: Some("m"), - // hard_width: None, - // max_soft_width: None, - }, - ); - } - ProcessName => { - column_mapping.insert( - column, - ColumnInfo { - enabled: true, - shortcut: Some("n"), - // hard_width: None, - // max_soft_width: None, - }, - ); - } - Command => { - column_mapping.insert( - column, - ColumnInfo { - enabled: false, - shortcut: Some("n"), - // hard_width: None, - // max_soft_width: None, - }, - ); - } - Pid => { - column_mapping.insert( - column, - ColumnInfo { - enabled: true, - shortcut: Some("p"), - // hard_width: None, - // max_soft_width: None, - }, - ); - } - Count => { - column_mapping.insert( - column, - ColumnInfo { - enabled: false, - shortcut: None, - // hard_width: None, - // max_soft_width: None, - }, - ); - } - User => { - column_mapping.insert( - column, - ColumnInfo { - enabled: cfg!(target_family = "unix"), - shortcut: None, - }, - ); - } - _ => { - column_mapping.insert( - column, - ColumnInfo { - enabled: true, - shortcut: None, - // hard_width: None, - // max_soft_width: None, - }, - ); - } - } - } - let longest_header_len = longest_header_len as u16; - - ProcColumn { - ordered_columns, - column_mapping, - longest_header_len, - column_state: TableState::default(), - scroll_direction: ScrollDirection::default(), - current_scroll_position: 0, - previous_scroll_position: 0, - backup_prev_scroll_position: 0, - column_header_y_loc: None, - column_header_x_locs: None, - } - } -} - -impl ProcColumn { - /// Returns its new status. - pub fn toggle(&mut self, column: &ProcessSorting) -> Option { - if let Some(mapping) = self.column_mapping.get_mut(column) { - mapping.enabled = !(mapping.enabled); - Some(mapping.enabled) - } else { - None - } - } - - pub fn try_set(&mut self, column: &ProcessSorting, setting: bool) -> Option { - if let Some(mapping) = self.column_mapping.get_mut(column) { - mapping.enabled = setting; - Some(mapping.enabled) - } else { - None - } - } - - pub fn try_enable(&mut self, column: &ProcessSorting) -> Option { - if let Some(mapping) = self.column_mapping.get_mut(column) { - mapping.enabled = true; - Some(mapping.enabled) - } else { - None - } - } - - pub fn try_disable(&mut self, column: &ProcessSorting) -> Option { - if let Some(mapping) = self.column_mapping.get_mut(column) { - mapping.enabled = false; - Some(mapping.enabled) - } else { - None - } - } - - pub fn is_enabled(&self, column: &ProcessSorting) -> bool { - if let Some(mapping) = self.column_mapping.get(column) { - mapping.enabled - } else { - false - } - } - - pub fn get_enabled_columns_len(&self) -> usize { - self.ordered_columns - .iter() - .filter_map(|column_type| { - if let Some(col_map) = self.column_mapping.get(column_type) { - if col_map.enabled { - Some(1) - } else { - None - } - } else { - None - } - }) - .sum() - } - - /// NOTE: ALWAYS call this when opening the sorted window. - pub fn set_to_sorted_index_from_type(&mut self, proc_sorting_type: &ProcessSorting) { - // TODO [Custom Columns]: If we add custom columns, this may be needed! Since column indices will change, this runs the risk of OOB. So, when you change columns, CALL THIS AND ADAPT! - let mut true_index = 0; - for column in &self.ordered_columns { - if *column == *proc_sorting_type { - break; - } - if self.column_mapping.get(column).unwrap().enabled { - true_index += 1; - } - } - - self.current_scroll_position = true_index; - self.backup_prev_scroll_position = self.previous_scroll_position; - } - - /// This function sets the scroll position based on the index. - pub fn set_to_sorted_index_from_visual_index(&mut self, visual_index: usize) { - self.current_scroll_position = visual_index; - self.backup_prev_scroll_position = self.previous_scroll_position; - } - - pub fn get_column_headers( - &self, proc_sorting_type: &ProcessSorting, sort_reverse: bool, - ) -> Vec { - const DOWN_ARROW: char = '▼'; - const UP_ARROW: char = '▲'; - - // TODO: Gonna have to figure out how to do left/right GUI notation if we add it. - self.ordered_columns - .iter() - .filter_map(|column_type| { - let mapping = self.column_mapping.get(column_type).unwrap(); - let mut command_str = String::default(); - if let Some(command) = mapping.shortcut { - command_str = format!("({})", command); - } - - if mapping.enabled { - Some(format!( - "{}{}{}", - column_type.to_string(), - command_str.as_str(), - if proc_sorting_type == column_type { - if sort_reverse { - DOWN_ARROW - } else { - UP_ARROW - } - } else { - ' ' - } - )) - } else { - None - } - }) - .collect() - } -} - -pub struct ProcWidgetState { - pub process_search_state: ProcessSearchState, - pub is_grouped: bool, - pub scroll_state: AppScrollWidgetState, - pub process_sorting_type: processes::ProcessSorting, - pub is_process_sort_descending: bool, - pub is_using_command: bool, - pub current_column_index: usize, - pub is_sort_open: bool, - pub columns: ProcColumn, - pub is_tree_mode: bool, - pub table_width_state: CanvasTableWidthState, - pub requires_redraw: bool, -} - -impl ProcWidgetState { - pub fn init( - is_case_sensitive: bool, is_match_whole_word: bool, is_use_regex: bool, is_grouped: bool, - show_memory_as_values: bool, is_tree_mode: bool, is_using_command: bool, - ) -> Self { - let mut process_search_state = ProcessSearchState::default(); - - if is_case_sensitive { - // By default it's off - process_search_state.search_toggle_ignore_case(); - } - if is_match_whole_word { - process_search_state.search_toggle_whole_word(); - } - if is_use_regex { - process_search_state.search_toggle_regex(); - } - - let (process_sorting_type, is_process_sort_descending) = if is_tree_mode { - (processes::ProcessSorting::Pid, false) - } else { - (processes::ProcessSorting::CpuPercent, true) - }; - - // TODO: If we add customizable columns, this should pull from config - let mut columns = ProcColumn::default(); - columns.set_to_sorted_index_from_type(&process_sorting_type); - if is_grouped { - // Normally defaults to showing by PID, toggle count on instead. - columns.toggle(&ProcessSorting::Count); - columns.toggle(&ProcessSorting::Pid); - } - if show_memory_as_values { - // Normally defaults to showing by percent, toggle value on instead. - columns.toggle(&ProcessSorting::Mem); - columns.toggle(&ProcessSorting::MemPercent); - } - - ProcWidgetState { - process_search_state, - is_grouped, - scroll_state: AppScrollWidgetState::default(), - process_sorting_type, - is_process_sort_descending, - is_using_command, - current_column_index: 0, - is_sort_open: false, - columns, - is_tree_mode, - table_width_state: CanvasTableWidthState::default(), - requires_redraw: false, - } - } - - /// Updates sorting when using the column list. - /// ...this really should be part of the ProcColumn struct (along with the sorting fields), - /// but I'm too lazy. - /// - /// Sorry, future me, you're gonna have to refactor this later. Too busy getting - /// the feature to work in the first place! :) - pub fn update_sorting_with_columns(&mut self) { - let mut true_index = 0; - let mut enabled_index = 0; - let target_itx = self.columns.current_scroll_position; - for column in &self.columns.ordered_columns { - let enabled = self.columns.column_mapping.get(column).unwrap().enabled; - if enabled_index == target_itx && enabled { - break; - } - if enabled { - enabled_index += 1; - } - true_index += 1; - } - - if let Some(new_sort_type) = self.columns.ordered_columns.get(true_index) { - if *new_sort_type == self.process_sorting_type { - // Just reverse the search if we're reselecting! - self.is_process_sort_descending = !(self.is_process_sort_descending); - } else { - self.process_sorting_type = new_sort_type.clone(); - match self.process_sorting_type { - ProcessSorting::State - | ProcessSorting::Pid - | ProcessSorting::ProcessName - | ProcessSorting::Command => { - // Also invert anything that uses alphabetical sorting by default. - self.is_process_sort_descending = false; - } - _ => { - self.is_process_sort_descending = true; - } - } - } - } - } - - pub fn toggle_command_and_name(&mut self, is_using_command: bool) { - if let Some(pn) = self - .columns - .column_mapping - .get_mut(&ProcessSorting::ProcessName) - { - pn.enabled = !is_using_command; - } - if let Some(c) = self - .columns - .column_mapping - .get_mut(&ProcessSorting::Command) - { - c.enabled = is_using_command; - } - } - - pub fn get_search_cursor_position(&self) -> usize { - self.process_search_state - .search_state - .grapheme_cursor - .cur_cursor() - } - - pub fn get_char_cursor_position(&self) -> usize { - self.process_search_state.search_state.char_cursor_position - } - - pub fn is_search_enabled(&self) -> bool { - self.process_search_state.search_state.is_enabled - } - - pub fn get_current_search_query(&self) -> &String { - &self.process_search_state.search_state.current_search_query - } - - pub fn update_query(&mut self) { - if self - .process_search_state - .search_state - .current_search_query - .is_empty() - { - self.process_search_state.search_state.is_blank_search = true; - self.process_search_state.search_state.is_invalid_search = false; - self.process_search_state.search_state.error_message = None; - } else { - let parsed_query = self.parse_query(); - // debug!("Parsed query: {:#?}", parsed_query); - - if let Ok(parsed_query) = parsed_query { - self.process_search_state.search_state.query = Some(parsed_query); - self.process_search_state.search_state.is_blank_search = false; - self.process_search_state.search_state.is_invalid_search = false; - self.process_search_state.search_state.error_message = None; - } else if let Err(err) = parsed_query { - self.process_search_state.search_state.is_blank_search = false; - self.process_search_state.search_state.is_invalid_search = true; - self.process_search_state.search_state.error_message = Some(err.to_string()); - } - } - self.scroll_state.previous_scroll_position = 0; - self.scroll_state.current_scroll_position = 0; - } - - pub fn clear_search(&mut self) { - self.process_search_state.search_state.reset(); - } - - pub fn search_walk_forward(&mut self, start_position: usize) { - self.process_search_state - .search_state - .grapheme_cursor - .next_boundary( - &self.process_search_state.search_state.current_search_query[start_position..], - start_position, - ) - .unwrap(); - } - - pub fn search_walk_back(&mut self, start_position: usize) { - self.process_search_state - .search_state - .grapheme_cursor - .prev_boundary( - &self.process_search_state.search_state.current_search_query[..start_position], - 0, - ) - .unwrap(); - } -} - -pub struct ProcState { - pub widget_states: HashMap, - pub force_update: Option, - pub force_update_all: bool, -} - -impl ProcState { - pub fn init(widget_states: HashMap) -> Self { - ProcState { - widget_states, - force_update: None, - force_update_all: false, - } - } - - pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut ProcWidgetState> { - self.widget_states.get_mut(&widget_id) - } - - pub fn get_widget_state(&self, widget_id: u64) -> Option<&ProcWidgetState> { - self.widget_states.get(&widget_id) - } -} - -pub struct NetWidgetState { - pub current_display_time: u64, - pub autohide_timer: Option, - // pub draw_max_range_cache: f64, - // pub draw_labels_cache: Vec, - // pub draw_time_start_cache: f64, - // TODO: Re-enable these when we move net details state-side! - // pub unit_type: DataUnitTypes, - // pub scale_type: AxisScaling, -} - -impl NetWidgetState { - pub fn init( - current_display_time: u64, - autohide_timer: Option, - // unit_type: DataUnitTypes, - // scale_type: AxisScaling, - ) -> Self { - NetWidgetState { - current_display_time, - autohide_timer, - // draw_max_range_cache: 0.0, - // draw_labels_cache: vec![], - // draw_time_start_cache: 0.0, - // unit_type, - // scale_type, - } - } -} - -pub struct NetState { - pub force_update: Option, - pub widget_states: HashMap, -} - -impl NetState { - pub fn init(widget_states: HashMap) -> Self { - NetState { - force_update: None, - widget_states, - } - } - - pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut NetWidgetState> { - self.widget_states.get_mut(&widget_id) - } - - pub fn get_widget_state(&self, widget_id: u64) -> Option<&NetWidgetState> { - self.widget_states.get(&widget_id) - } -} - -pub struct CpuWidgetState { - pub current_display_time: u64, - pub is_legend_hidden: bool, - pub autohide_timer: Option, - pub scroll_state: AppScrollWidgetState, - pub is_multi_graph_mode: bool, - pub table_width_state: CanvasTableWidthState, -} - -impl CpuWidgetState { - pub fn init(current_display_time: u64, autohide_timer: Option) -> Self { - CpuWidgetState { - current_display_time, - is_legend_hidden: false, - autohide_timer, - scroll_state: AppScrollWidgetState::default(), - is_multi_graph_mode: false, - table_width_state: CanvasTableWidthState::default(), - } - } -} - -pub struct CpuState { - pub force_update: Option, - pub widget_states: HashMap, -} - -impl CpuState { - pub fn init(widget_states: HashMap) -> Self { - CpuState { - force_update: None, - widget_states, - } - } - - pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut CpuWidgetState> { - self.widget_states.get_mut(&widget_id) - } - - pub fn get_widget_state(&self, widget_id: u64) -> Option<&CpuWidgetState> { - self.widget_states.get(&widget_id) - } -} - -pub struct MemWidgetState { - pub current_display_time: u64, - pub autohide_timer: Option, -} - -impl MemWidgetState { - pub fn init(current_display_time: u64, autohide_timer: Option) -> Self { - MemWidgetState { - current_display_time, - autohide_timer, - } - } -} -pub struct MemState { - pub force_update: Option, - pub widget_states: HashMap, -} - -impl MemState { - pub fn init(widget_states: HashMap) -> Self { - MemState { - force_update: None, - widget_states, - } - } - - pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut MemWidgetState> { - self.widget_states.get_mut(&widget_id) - } - - pub fn get_widget_state(&self, widget_id: u64) -> Option<&MemWidgetState> { - self.widget_states.get(&widget_id) - } -} - -pub struct TempWidgetState { - pub scroll_state: AppScrollWidgetState, - pub table_width_state: CanvasTableWidthState, -} - -impl TempWidgetState { - pub fn init() -> Self { - TempWidgetState { - scroll_state: AppScrollWidgetState::default(), - table_width_state: CanvasTableWidthState::default(), - } - } -} - -pub struct TempState { - pub widget_states: HashMap, -} - -impl TempState { - pub fn init(widget_states: HashMap) -> Self { - TempState { widget_states } - } - - pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut TempWidgetState> { - self.widget_states.get_mut(&widget_id) - } - - pub fn get_widget_state(&self, widget_id: u64) -> Option<&TempWidgetState> { - self.widget_states.get(&widget_id) - } -} - -pub struct DiskWidgetState { - pub scroll_state: AppScrollWidgetState, - pub table_width_state: CanvasTableWidthState, -} - -impl DiskWidgetState { - pub fn init() -> Self { - DiskWidgetState { - scroll_state: AppScrollWidgetState::default(), - table_width_state: CanvasTableWidthState::default(), - } - } -} - -pub struct DiskState { - pub widget_states: HashMap, -} - -impl DiskState { - pub fn init(widget_states: HashMap) -> Self { - DiskState { widget_states } - } - - pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut DiskWidgetState> { - self.widget_states.get_mut(&widget_id) - } - - pub fn get_widget_state(&self, widget_id: u64) -> Option<&DiskWidgetState> { - self.widget_states.get(&widget_id) - } -} -pub struct BasicTableWidgetState { - // Since this is intended (currently) to only be used for ONE widget, that's - // how it's going to be written. If we want to allow for multiple of these, - // then we can expand outwards with a normal BasicTableState and a hashmap - pub currently_displayed_widget_type: BottomWidgetType, - pub currently_displayed_widget_id: u64, - pub widget_id: i64, - pub left_tlc: Option<(u16, u16)>, - pub left_brc: Option<(u16, u16)>, - pub right_tlc: Option<(u16, u16)>, - pub right_brc: Option<(u16, u16)>, -} - -#[derive(Default)] -pub struct BatteryWidgetState { - pub currently_selected_battery_index: usize, - pub tab_click_locs: Option>, -} - -pub struct BatteryState { - pub widget_states: HashMap, -} - -impl BatteryState { - pub fn init(widget_states: HashMap) -> Self { - BatteryState { widget_states } - } - - pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut BatteryWidgetState> { - self.widget_states.get_mut(&widget_id) - } - - pub fn get_widget_state(&self, widget_id: u64) -> Option<&BatteryWidgetState> { - self.widget_states.get(&widget_id) - } -} - -#[derive(Default)] -pub struct ParagraphScrollState { - pub current_scroll_index: u16, - pub max_scroll_index: u16, -} - -#[derive(Default)] -pub struct ConfigState { - pub current_category_index: usize, - pub category_list: Vec, -} - -#[derive(Default)] -pub struct ConfigCategory { - pub category_name: &'static str, - pub options_list: Vec, -} - -pub struct ConfigOption { - pub set_function: Box anyhow::Result<()>>, -} diff --git a/src/app/widgets.rs b/src/app/widgets.rs index 3e31b966..7ef2f4dd 100644 --- a/src/app/widgets.rs +++ b/src/app/widgets.rs @@ -2,13 +2,10 @@ use std::{fmt::Debug, time::Instant}; use crossterm::event::{KeyEvent, MouseEvent}; use enum_dispatch::enum_dispatch; -use tui::{backend::Backend, layout::Rect, widgets::TableState, Frame}; +use tui::{backend::Backend, layout::Rect, Frame}; use crate::{ - app::{ - event::{ComponentEventResult, SelectionAction}, - layout_manager::BottomWidgetType, - }, + app::event::{ComponentEventResult, SelectionAction}, canvas::Painter, options::layout_options::LayoutRule, }; @@ -26,7 +23,7 @@ pub use bottom_widgets::*; use self::tui_stuff::BlockBuilder; -use super::{data_farmer::DataCollection, event::EventResult}; +use super::data_farmer::DataCollection; /// A trait for things that are drawn with state. #[enum_dispatch] @@ -235,36 +232,7 @@ where } } -// ----- Old stuff below ----- - -#[derive(Debug)] -pub enum ScrollDirection { - // UP means scrolling up --- this usually DECREMENTS - Up, - // DOWN means scrolling down --- this usually INCREMENTS - Down, -} - -impl Default for ScrollDirection { - fn default() -> Self { - ScrollDirection::Down - } -} - -#[derive(Debug)] -pub enum CursorDirection { - Left, - Right, -} - -/// AppScrollWidgetState deals with fields for a scrollable app's current state. -#[derive(Default)] -pub struct AppScrollWidgetState { - pub current_scroll_position: usize, - pub previous_scroll_position: usize, - pub scroll_direction: ScrollDirection, - pub table_state: TableState, -} +// ----- FIXME: Delete the old stuff below ----- #[derive(PartialEq)] pub enum KillSignal { @@ -293,65 +261,3 @@ pub struct AppDeleteDialogState { pub last_number_press: Option, pub scroll_pos: usize, } - -pub struct AppHelpDialogState { - pub is_showing_help: bool, - pub scroll_state: ParagraphScrollState, - pub index_shortcuts: Vec, -} - -impl Default for AppHelpDialogState { - fn default() -> Self { - AppHelpDialogState { - is_showing_help: false, - scroll_state: ParagraphScrollState::default(), - index_shortcuts: vec![], - } - } -} - -impl AppHelpDialogState { - pub fn increment(&mut self) -> EventResult { - if self.scroll_state.current_scroll_index < self.scroll_state.max_scroll_index { - self.scroll_state.current_scroll_index += 1; - EventResult::Redraw - } else { - EventResult::NoRedraw - } - } - - pub fn decrement(&mut self) -> EventResult { - if self.scroll_state.current_scroll_index > 0 { - self.scroll_state.current_scroll_index -= 1; - EventResult::Redraw - } else { - EventResult::NoRedraw - } - } -} - -/// Meant for canvas operations involving table column widths. -#[derive(Default)] -pub struct CanvasTableWidthState { - pub desired_column_widths: Vec, - pub calculated_column_widths: Vec, -} - -pub struct BasicTableWidgetState { - // Since this is intended (currently) to only be used for ONE widget, that's - // how it's going to be written. If we want to allow for multiple of these, - // then we can expand outwards with a normal BasicTableState and a hashmap - pub currently_displayed_widget_type: BottomWidgetType, - pub currently_displayed_widget_id: u64, - pub widget_id: i64, - pub left_tlc: Option<(u16, u16)>, - pub left_brc: Option<(u16, u16)>, - pub right_tlc: Option<(u16, u16)>, - pub right_brc: Option<(u16, u16)>, -} - -#[derive(Default)] -pub struct ParagraphScrollState { - pub current_scroll_index: u16, - pub max_scroll_index: u16, -} diff --git a/src/app/widgets/base/scrollable.rs b/src/app/widgets/base/scrollable.rs index b8652a9f..5d5620ae 100644 --- a/src/app/widgets/base/scrollable.rs +++ b/src/app/widgets/base/scrollable.rs @@ -57,7 +57,7 @@ impl Scrollable { scroll_direction: ScrollDirection::Down, num_items, tui_state, - gg_manager: MultiKey::register(vec!['g', 'g']), // TODO: Use a static arrayvec + gg_manager: MultiKey::register(vec!['g', 'g']), // TODO: [Optimization] Use a static arrayvec bounds: Rect::default(), } } diff --git a/src/app/widgets/base/sort_text_table.rs b/src/app/widgets/base/sort_text_table.rs index f1f3b0e4..c8ff4d41 100644 --- a/src/app/widgets/base/sort_text_table.rs +++ b/src/app/widgets/base/sort_text_table.rs @@ -5,7 +5,7 @@ use tui::{backend::Backend, layout::Rect, Frame}; use crate::{ app::{ - event::{ReturnSignal, ComponentEventResult}, + event::{ComponentEventResult, ReturnSignal}, widgets::tui_stuff::BlockBuilder, Component, TextTable, }, diff --git a/src/app/widgets/base/text_input.rs b/src/app/widgets/base/text_input.rs index 32106a15..2b0d0036 100644 --- a/src/app/widgets/base/text_input.rs +++ b/src/app/widgets/base/text_input.rs @@ -262,7 +262,7 @@ impl TextInput { let after_cursor = graphemes.map(|(_, grapheme)| grapheme).collect::(); - // FIXME: This is NOT done! This is an incomplete (but kinda working) implementation, for now. + // FIXME: [AFTER REFACTOR] This is NOT done! This is an incomplete (but kinda working) implementation, for now. let search_text = vec![Spans::from(vec![ Span::styled( @@ -365,10 +365,10 @@ impl Component for TextInput { fn handle_mouse_event(&mut self, _event: MouseEvent) -> ComponentEventResult { // We are assuming this is within bounds... + // TODO: [Feature] Add mouse input for text input cursor // let x = event.column; // let widget_x = self.bounds.x + 2; // if x >= widget_x { - // // TODO: Do this at some point after refactor // ComponentEventResult::Redraw // } else { // ComponentEventResult::NoRedraw diff --git a/src/app/widgets/base/text_table.rs b/src/app/widgets/base/text_table.rs index e6d42001..e8281b78 100644 --- a/src/app/widgets/base/text_table.rs +++ b/src/app/widgets/base/text_table.rs @@ -46,8 +46,6 @@ pub type TextTableDataRef = [Vec<(Cow<'static, str>, Option>, #[derive(Debug)] pub struct SimpleColumn { name: Cow<'static, str>, - - // TODO: I would remove these in the future, storing them here feels weird... desired_width: DesiredColumnWidth, x_bounds: Option<(u16, u16)>, } @@ -130,7 +128,7 @@ where pub show_gap: bool, /// The bounding box of the [`TextTable`]. - pub bounds: Rect, // TODO: Consider moving bounds to something else? + pub bounds: Rect, // TODO: [Refactor, Drawing] Consider moving bounds to something else? /// The bounds including the border, if there is one. pub border_bounds: Rect, diff --git a/src/app/widgets/base/time_graph.rs b/src/app/widgets/base/time_graph.rs index 47436f71..cc2a8c69 100644 --- a/src/app/widgets/base/time_graph.rs +++ b/src/app/widgets/base/time_graph.rs @@ -45,7 +45,7 @@ pub enum AutohideTimer { }, } -// TODO: [AUTOHIDE] Not a fan of how this is done, as this should really "trigger" a draw when it's done. +// TODO: [Refactor] Not a fan of how autohide is currently done, as this should really "trigger" a draw when it's done. Maybe use async/threads? impl AutohideTimer { fn start_display_timer(&mut self) { match self { diff --git a/src/app/widgets/bottom_widgets/basic_mem.rs b/src/app/widgets/bottom_widgets/basic_mem.rs index 3658ee7c..5b151001 100644 --- a/src/app/widgets/bottom_widgets/basic_mem.rs +++ b/src/app/widgets/bottom_widgets/basic_mem.rs @@ -128,7 +128,7 @@ impl Widget for BasicMem { fn update_data(&mut self, data_collection: &DataCollection) { let (memory_labels, swap_labels) = convert_mem_labels(data_collection); - // TODO: [Data update optimization] Probably should just make another function altogether for basic mode. + // TODO: [Optimization] Probably should just make another function altogether for just basic mem mode. self.mem_data = if let (Some(data), Some((_, fraction))) = ( convert_mem_data_points(data_collection).last(), memory_labels, diff --git a/src/app/widgets/bottom_widgets/battery.rs b/src/app/widgets/bottom_widgets/battery.rs index 8c1d0721..1f9b1a88 100644 --- a/src/app/widgets/bottom_widgets/battery.rs +++ b/src/app/widgets/bottom_widgets/battery.rs @@ -1,7 +1,4 @@ -use std::{ - cmp::{max, min}, - collections::HashMap, -}; +use std::cmp::{max, min}; use crossterm::event::{KeyCode, KeyEvent, MouseEvent}; use tui::{ @@ -23,23 +20,6 @@ use crate::{ options::layout_options::LayoutRule, }; -#[derive(Default)] -pub struct BatteryWidgetState { - pub currently_selected_battery_index: usize, - pub tab_click_locs: Option>, -} - -#[derive(Default)] -pub struct BatteryState { - pub widget_states: HashMap, -} - -impl BatteryState { - pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut BatteryWidgetState> { - self.widget_states.get_mut(&widget_id) - } -} - /// A table displaying battery information on a per-battery basis. pub struct BatteryTable { bounds: Rect, diff --git a/src/app/widgets/bottom_widgets/cpu.rs b/src/app/widgets/bottom_widgets/cpu.rs index 6e5e18a5..d82f8500 100644 --- a/src/app/widgets/bottom_widgets/cpu.rs +++ b/src/app/widgets/bottom_widgets/cpu.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, collections::HashMap, time::Instant}; +use std::borrow::Cow; use crossterm::event::{KeyEvent, MouseEvent}; use tui::{ @@ -12,39 +12,13 @@ use crate::{ event::{ComponentEventResult, SelectionAction}, text_table::SimpleColumn, time_graph::TimeGraphData, - AppConfigFields, AppScrollWidgetState, CanvasTableWidthState, Component, DataCollection, - TextTable, TimeGraph, Widget, + AppConfigFields, Component, DataCollection, TextTable, TimeGraph, Widget, }, canvas::Painter, data_conversion::{convert_cpu_data_points, ConvertedCpuData}, options::layout_options::LayoutRule, }; -pub struct CpuWidgetState { - pub current_display_time: u64, - pub is_legend_hidden: bool, - pub autohide_timer: Option, - pub scroll_state: AppScrollWidgetState, - pub is_multi_graph_mode: bool, - pub table_width_state: CanvasTableWidthState, -} - -#[derive(Default)] -pub struct CpuState { - pub force_update: Option, - pub widget_states: HashMap, -} - -impl CpuState { - pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut CpuWidgetState> { - self.widget_states.get_mut(&widget_id) - } - - pub fn get_widget_state(&self, widget_id: u64) -> Option<&CpuWidgetState> { - self.widget_states.get(&widget_id) - } -} - /// Which part of the [`CpuGraph`] is currently selected. enum CpuGraphSelection { Graph, @@ -228,7 +202,7 @@ impl Widget for CpuGraph { }) .collect::>(); - // TODO: You MUST draw the table first, otherwise the index may mismatch after a reset. This is a bad gotcha - we should look into auto-updating the table's length! + // TODO: [Gotcha, Refactor] You MUST draw the table first, otherwise the index may mismatch after a reset. This is a bad gotcha - we should look into auto-updating the table's length! self.legend.draw_tui_table( painter, f, diff --git a/src/app/widgets/bottom_widgets/disk.rs b/src/app/widgets/bottom_widgets/disk.rs index 74b63f86..c3ec8bcd 100644 --- a/src/app/widgets/bottom_widgets/disk.rs +++ b/src/app/widgets/bottom_widgets/disk.rs @@ -1,43 +1,17 @@ -use std::collections::HashMap; - use crossterm::event::{KeyEvent, MouseEvent}; use tui::{backend::Backend, layout::Rect, widgets::Borders, Frame}; use crate::{ app::{ data_farmer::DataCollection, event::ComponentEventResult, - sort_text_table::SimpleSortableColumn, text_table::TextTableData, AppScrollWidgetState, - CanvasTableWidthState, Component, TextTable, Widget, + sort_text_table::SimpleSortableColumn, text_table::TextTableData, Component, TextTable, + Widget, }, canvas::Painter, data_conversion::convert_disk_row, options::layout_options::LayoutRule, }; -pub struct DiskWidgetState { - pub scroll_state: AppScrollWidgetState, - pub table_width_state: CanvasTableWidthState, -} - -#[derive(Default)] -pub struct DiskState { - pub widget_states: HashMap, -} - -impl DiskState { - pub fn init(widget_states: HashMap) -> Self { - DiskState { widget_states } - } - - pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut DiskWidgetState> { - self.widget_states.get_mut(&widget_id) - } - - pub fn get_widget_state(&self, widget_id: u64) -> Option<&DiskWidgetState> { - self.widget_states.get(&widget_id) - } -} - /// A table displaying disk data. pub struct DiskTable { table: TextTable, diff --git a/src/app/widgets/bottom_widgets/mem.rs b/src/app/widgets/bottom_widgets/mem.rs index 465e3ab9..c4ada423 100644 --- a/src/app/widgets/bottom_widgets/mem.rs +++ b/src/app/widgets/bottom_widgets/mem.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, collections::HashMap, time::Instant}; +use std::borrow::Cow; use crossterm::event::{KeyEvent, MouseEvent}; use tui::{backend::Backend, layout::Rect}; @@ -10,17 +10,6 @@ use crate::{ options::layout_options::LayoutRule, }; -pub struct MemWidgetState { - pub current_display_time: u64, - pub autohide_timer: Option, -} - -#[derive(Default)] -pub struct MemState { - pub force_update: Option, - pub widget_states: HashMap, -} - /// A widget that deals with displaying memory usage on a [`TimeGraph`]. Basically just a wrapper /// around [`TimeGraph`] as of now. pub struct MemGraph { diff --git a/src/app/widgets/bottom_widgets/net.rs b/src/app/widgets/bottom_widgets/net.rs index 1449aa6a..4be88045 100644 --- a/src/app/widgets/bottom_widgets/net.rs +++ b/src/app/widgets/bottom_widgets/net.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, collections::HashMap, time::Instant}; +use std::borrow::Cow; use crossterm::event::{KeyEvent, MouseEvent}; use tui::{ @@ -20,19 +20,6 @@ use crate::{ utils::gen_util::*, }; -pub struct NetWidgetState { - pub current_display_time: u64, - pub autohide_timer: Option, -} - -#[derive(Default)] -pub struct NetState { - pub force_update: Option, - pub widget_states: HashMap, -} - -// --- NEW STUFF BELOW --- - /// Returns the max data point and time given a time. fn get_max_entry( rx: &[(f64, f64)], tx: &[(f64, f64)], time_start: f64, network_scale_type: &AxisScaling, diff --git a/src/app/widgets/bottom_widgets/process.rs b/src/app/widgets/bottom_widgets/process.rs index 3851bfcd..074370b3 100644 --- a/src/app/widgets/bottom_widgets/process.rs +++ b/src/app/widgets/bottom_widgets/process.rs @@ -4,13 +4,12 @@ use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEvent, use float_ord::FloatOrd; use itertools::{Either, Itertools}; use once_cell::unsync::Lazy; -use unicode_segmentation::GraphemeCursor; use tui::{ backend::Backend, layout::{Constraint, Direction, Layout, Rect}, text::{Span, Spans}, - widgets::{Borders, Paragraph, TableState}, + widgets::{Borders, Paragraph}, Frame, }; @@ -25,557 +24,17 @@ use crate::{ }, canvas::Painter, data_conversion::get_string_with_bytes, - data_harvester::processes::{self, ProcessSorting}, options::{layout_options::LayoutRule, ProcessDefaults}, utils::error::BottomError, }; -use ProcessSorting::*; use crate::app::{ does_bound_intersect_coordinate, sort_text_table::{SimpleSortableColumn, SortStatus, SortableColumn}, text_table::TextTableData, - AppScrollWidgetState, CanvasTableWidthState, Component, CursorDirection, ScrollDirection, - SortMenu, SortableTextTable, TextInput, Widget, + Component, SortMenu, SortableTextTable, TextInput, Widget, }; -/// AppSearchState deals with generic searching (I might do this in the future). -pub struct AppSearchState { - pub is_enabled: bool, - pub current_search_query: String, - pub is_blank_search: bool, - pub is_invalid_search: bool, - pub grapheme_cursor: GraphemeCursor, - pub cursor_direction: CursorDirection, - pub cursor_bar: usize, - /// This represents the position in terms of CHARACTERS, not graphemes - pub char_cursor_position: usize, - /// The query - pub query: Option, - pub error_message: Option, -} - -impl Default for AppSearchState { - fn default() -> Self { - AppSearchState { - is_enabled: false, - current_search_query: String::default(), - is_invalid_search: false, - is_blank_search: true, - grapheme_cursor: GraphemeCursor::new(0, 0, true), - cursor_direction: CursorDirection::Right, - cursor_bar: 0, - char_cursor_position: 0, - query: None, - error_message: None, - } - } -} - -impl AppSearchState { - /// Returns a reset but still enabled app search state - pub fn reset(&mut self) { - *self = AppSearchState { - is_enabled: self.is_enabled, - ..AppSearchState::default() - } - } - - pub fn is_invalid_or_blank_search(&self) -> bool { - self.is_blank_search || self.is_invalid_search - } -} - -/// 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; - } -} - -pub struct ColumnInfo { - pub enabled: bool, - pub shortcut: Option<&'static str>, -} - -pub struct ProcColumn { - pub ordered_columns: Vec, - /// The y location of headers. Since they're all aligned, it's just one value. - pub column_header_y_loc: Option, - /// The x start and end bounds for each header. - pub column_header_x_locs: Option>, - pub column_mapping: HashMap, - pub longest_header_len: u16, - pub column_state: TableState, - pub scroll_direction: ScrollDirection, - pub current_scroll_position: usize, - pub previous_scroll_position: usize, - pub backup_prev_scroll_position: usize, -} - -impl Default for ProcColumn { - fn default() -> Self { - let ordered_columns = vec![ - Count, - Pid, - ProcessName, - Command, - CpuPercent, - Mem, - MemPercent, - ReadPerSecond, - WritePerSecond, - TotalRead, - TotalWrite, - User, - State, - ]; - - let mut column_mapping = HashMap::new(); - let mut longest_header_len = 0; - for column in ordered_columns.clone() { - longest_header_len = std::cmp::max(longest_header_len, column.to_string().len()); - match column { - CpuPercent => { - column_mapping.insert( - column, - ColumnInfo { - enabled: true, - shortcut: Some("c"), - // hard_width: None, - // max_soft_width: None, - }, - ); - } - MemPercent => { - column_mapping.insert( - column, - ColumnInfo { - enabled: true, - shortcut: Some("m"), - // hard_width: None, - // max_soft_width: None, - }, - ); - } - Mem => { - column_mapping.insert( - column, - ColumnInfo { - enabled: false, - shortcut: Some("m"), - // hard_width: None, - // max_soft_width: None, - }, - ); - } - ProcessName => { - column_mapping.insert( - column, - ColumnInfo { - enabled: true, - shortcut: Some("n"), - // hard_width: None, - // max_soft_width: None, - }, - ); - } - Command => { - column_mapping.insert( - column, - ColumnInfo { - enabled: false, - shortcut: Some("n"), - // hard_width: None, - // max_soft_width: None, - }, - ); - } - Pid => { - column_mapping.insert( - column, - ColumnInfo { - enabled: true, - shortcut: Some("p"), - // hard_width: None, - // max_soft_width: None, - }, - ); - } - Count => { - column_mapping.insert( - column, - ColumnInfo { - enabled: false, - shortcut: None, - // hard_width: None, - // max_soft_width: None, - }, - ); - } - User => { - column_mapping.insert( - column, - ColumnInfo { - enabled: cfg!(target_family = "unix"), - shortcut: None, - }, - ); - } - _ => { - column_mapping.insert( - column, - ColumnInfo { - enabled: true, - shortcut: None, - // hard_width: None, - // max_soft_width: None, - }, - ); - } - } - } - let longest_header_len = longest_header_len as u16; - - ProcColumn { - ordered_columns, - column_mapping, - longest_header_len, - column_state: TableState::default(), - scroll_direction: ScrollDirection::default(), - current_scroll_position: 0, - previous_scroll_position: 0, - backup_prev_scroll_position: 0, - column_header_y_loc: None, - column_header_x_locs: None, - } - } -} - -impl ProcColumn { - /// Returns its new status. - pub fn toggle(&mut self, column: &ProcessSorting) -> Option { - if let Some(mapping) = self.column_mapping.get_mut(column) { - mapping.enabled = !(mapping.enabled); - Some(mapping.enabled) - } else { - None - } - } - - pub fn try_set(&mut self, column: &ProcessSorting, setting: bool) -> Option { - if let Some(mapping) = self.column_mapping.get_mut(column) { - mapping.enabled = setting; - Some(mapping.enabled) - } else { - None - } - } - - pub fn try_enable(&mut self, column: &ProcessSorting) -> Option { - if let Some(mapping) = self.column_mapping.get_mut(column) { - mapping.enabled = true; - Some(mapping.enabled) - } else { - None - } - } - - pub fn try_disable(&mut self, column: &ProcessSorting) -> Option { - if let Some(mapping) = self.column_mapping.get_mut(column) { - mapping.enabled = false; - Some(mapping.enabled) - } else { - None - } - } - - pub fn is_enabled(&self, column: &ProcessSorting) -> bool { - if let Some(mapping) = self.column_mapping.get(column) { - mapping.enabled - } else { - false - } - } - - pub fn get_enabled_columns_len(&self) -> usize { - self.ordered_columns - .iter() - .filter_map(|column_type| { - if let Some(col_map) = self.column_mapping.get(column_type) { - if col_map.enabled { - Some(1) - } else { - None - } - } else { - None - } - }) - .sum() - } - - /// NOTE: ALWAYS call this when opening the sorted window. - pub fn set_to_sorted_index_from_type(&mut self, proc_sorting_type: &ProcessSorting) { - // TODO [Custom Columns]: If we add custom columns, this may be needed! Since column indices will change, this runs the risk of OOB. So, when you change columns, CALL THIS AND ADAPT! - let mut true_index = 0; - for column in &self.ordered_columns { - if *column == *proc_sorting_type { - break; - } - if self.column_mapping.get(column).unwrap().enabled { - true_index += 1; - } - } - - self.current_scroll_position = true_index; - self.backup_prev_scroll_position = self.previous_scroll_position; - } - - /// This function sets the scroll position based on the index. - pub fn set_to_sorted_index_from_visual_index(&mut self, visual_index: usize) { - self.current_scroll_position = visual_index; - self.backup_prev_scroll_position = self.previous_scroll_position; - } - - pub fn get_column_headers( - &self, proc_sorting_type: &ProcessSorting, sort_reverse: bool, - ) -> Vec { - const DOWN_ARROW: char = '▼'; - const UP_ARROW: char = '▲'; - - // TODO: Gonna have to figure out how to do left/right GUI notation if we add it. - self.ordered_columns - .iter() - .filter_map(|column_type| { - let mapping = self.column_mapping.get(column_type).unwrap(); - let mut command_str = String::default(); - if let Some(command) = mapping.shortcut { - command_str = format!("({})", command); - } - - if mapping.enabled { - Some(format!( - "{}{}{}", - column_type.to_string(), - command_str.as_str(), - if proc_sorting_type == column_type { - if sort_reverse { - DOWN_ARROW - } else { - UP_ARROW - } - } else { - ' ' - } - )) - } else { - None - } - }) - .collect() - } -} - -pub struct ProcWidgetState { - pub process_search_state: ProcessSearchState, - pub is_grouped: bool, - pub scroll_state: AppScrollWidgetState, - pub process_sorting_type: processes::ProcessSorting, - pub is_process_sort_descending: bool, - pub is_using_command: bool, - pub current_column_index: usize, - pub is_sort_open: bool, - pub columns: ProcColumn, - pub is_tree_mode: bool, - pub table_width_state: CanvasTableWidthState, - pub requires_redraw: bool, -} - -impl ProcWidgetState { - /// Updates sorting when using the column list. - /// ...this really should be part of the ProcColumn struct (along with the sorting fields), - /// but I'm too lazy. - /// - /// Sorry, future me, you're gonna have to refactor this later. Too busy getting - /// the feature to work in the first place! :) - pub fn update_sorting_with_columns(&mut self) { - let mut true_index = 0; - let mut enabled_index = 0; - let target_itx = self.columns.current_scroll_position; - for column in &self.columns.ordered_columns { - let enabled = self.columns.column_mapping.get(column).unwrap().enabled; - if enabled_index == target_itx && enabled { - break; - } - if enabled { - enabled_index += 1; - } - true_index += 1; - } - - if let Some(new_sort_type) = self.columns.ordered_columns.get(true_index) { - if *new_sort_type == self.process_sorting_type { - // Just reverse the search if we're reselecting! - self.is_process_sort_descending = !(self.is_process_sort_descending); - } else { - self.process_sorting_type = new_sort_type.clone(); - match self.process_sorting_type { - ProcessSorting::State - | ProcessSorting::Pid - | ProcessSorting::ProcessName - | ProcessSorting::Command => { - // Also invert anything that uses alphabetical sorting by default. - self.is_process_sort_descending = false; - } - _ => { - self.is_process_sort_descending = true; - } - } - } - } - } - - pub fn toggle_command_and_name(&mut self, is_using_command: bool) { - if let Some(pn) = self - .columns - .column_mapping - .get_mut(&ProcessSorting::ProcessName) - { - pn.enabled = !is_using_command; - } - if let Some(c) = self - .columns - .column_mapping - .get_mut(&ProcessSorting::Command) - { - c.enabled = is_using_command; - } - } - - pub fn get_search_cursor_position(&self) -> usize { - self.process_search_state - .search_state - .grapheme_cursor - .cur_cursor() - } - - pub fn get_char_cursor_position(&self) -> usize { - self.process_search_state.search_state.char_cursor_position - } - - pub fn is_search_enabled(&self) -> bool { - self.process_search_state.search_state.is_enabled - } - - pub fn get_current_search_query(&self) -> &String { - &self.process_search_state.search_state.current_search_query - } - - pub fn update_query(&mut self) { - if self - .process_search_state - .search_state - .current_search_query - .is_empty() - { - self.process_search_state.search_state.is_blank_search = true; - self.process_search_state.search_state.is_invalid_search = false; - self.process_search_state.search_state.error_message = None; - } else { - let parsed_query = parse_query( - self.get_current_search_query(), - self.process_search_state.is_searching_whole_word, - self.process_search_state.is_ignoring_case, - self.process_search_state.is_searching_with_regex, - ); - // debug!("Parsed query: {:#?}", parsed_query); - - if let Ok(parsed_query) = parsed_query { - self.process_search_state.search_state.query = Some(parsed_query); - self.process_search_state.search_state.is_blank_search = false; - self.process_search_state.search_state.is_invalid_search = false; - self.process_search_state.search_state.error_message = None; - } else if let Err(err) = parsed_query { - self.process_search_state.search_state.is_blank_search = false; - self.process_search_state.search_state.is_invalid_search = true; - self.process_search_state.search_state.error_message = Some(err.to_string()); - } - } - self.scroll_state.previous_scroll_position = 0; - self.scroll_state.current_scroll_position = 0; - } - - pub fn clear_search(&mut self) { - self.process_search_state.search_state.reset(); - } - - pub fn search_walk_forward(&mut self, start_position: usize) { - self.process_search_state - .search_state - .grapheme_cursor - .next_boundary( - &self.process_search_state.search_state.current_search_query[start_position..], - start_position, - ) - .unwrap(); - } - - pub fn search_walk_back(&mut self, start_position: usize) { - self.process_search_state - .search_state - .grapheme_cursor - .prev_boundary( - &self.process_search_state.search_state.current_search_query[..start_position], - 0, - ) - .unwrap(); - } -} - -#[derive(Default)] -pub struct ProcState { - pub widget_states: HashMap, - pub force_update: Option, - pub force_update_all: bool, -} - -impl ProcState { - pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut ProcWidgetState> { - self.widget_states.get_mut(&widget_id) - } - - pub fn get_widget_state(&self, widget_id: u64) -> Option<&ProcWidgetState> { - self.widget_states.get(&widget_id) - } -} - /// The currently selected part of a [`ProcessManager`] #[derive(PartialEq, Eq, Clone, Copy)] enum ProcessManagerSelection { @@ -835,7 +294,7 @@ impl ProcessManager { process_table: SortableTextTable::new(process_table_columns).default_sort_index(2), search_input: TextInput::default(), search_block_bounds: Rect::default(), - dd_multi: MultiKey::register(vec!['d', 'd']), // TODO: Maybe use something static... + 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, @@ -997,7 +456,7 @@ impl ProcessManager { } // Invalidate row cache. - self.process_table.invalidate_cached_columns(); // TODO: This should be automatically called somehow after sets/removes to avoid forgetting it - maybe do a queue system? + self.process_table.invalidate_cached_columns(); // TODO: [Gotcha, Refactor] This should be automatically called somehow after sets/removes to avoid forgetting it - maybe do a queue system? ComponentEventResult::Signal(ReturnSignal::Update) } @@ -1368,7 +827,7 @@ impl Widget for ProcessManager { f.render_widget( Paragraph::new(Spans::from(vec![ Span::styled(&*case_text, case_style), - Span::raw(" "), // TODO: Smartly space it out in the future... + Span::raw(" "), // TODO: [Drawing] Smartly space it out in the future... Span::styled(&*whole_word_text, whole_word_style), Span::raw(" "), Span::styled(&*regex_text, regex_style), diff --git a/src/app/widgets/bottom_widgets/temp.rs b/src/app/widgets/bottom_widgets/temp.rs index be8623f6..70faf862 100644 --- a/src/app/widgets/bottom_widgets/temp.rs +++ b/src/app/widgets/bottom_widgets/temp.rs @@ -1,5 +1,3 @@ -use std::collections::HashMap; - use crossterm::event::{KeyEvent, MouseEvent}; use tui::{backend::Backend, layout::Rect, widgets::Borders, Frame}; @@ -7,48 +5,14 @@ use crate::{ app::{ data_farmer::DataCollection, data_harvester::temperature::TemperatureType, event::ComponentEventResult, sort_text_table::SimpleSortableColumn, - text_table::TextTableData, AppScrollWidgetState, CanvasTableWidthState, Component, - TextTable, Widget, + text_table::TextTableData, Component, TextTable, Widget, }, canvas::Painter, data_conversion::convert_temp_row, options::layout_options::LayoutRule, }; -pub struct TempWidgetState { - pub scroll_state: AppScrollWidgetState, - pub table_width_state: CanvasTableWidthState, -} - -impl TempWidgetState { - pub fn init() -> Self { - TempWidgetState { - scroll_state: AppScrollWidgetState::default(), - table_width_state: CanvasTableWidthState::default(), - } - } -} - -#[derive(Default)] -pub struct TempState { - pub widget_states: HashMap, -} - -impl TempState { - pub fn init(widget_states: HashMap) -> Self { - TempState { widget_states } - } - - pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut TempWidgetState> { - self.widget_states.get_mut(&widget_id) - } - - pub fn get_widget_state(&self, widget_id: u64) -> Option<&TempWidgetState> { - self.widget_states.get(&widget_id) - } -} - -/// A table displaying disk data.. +/// A table displaying temperature data. pub struct TempTable { table: TextTable, bounds: Rect, diff --git a/src/app/widgets/dialogs/help.rs b/src/app/widgets/dialogs/help.rs index fba8a77c..478c857a 100644 --- a/src/app/widgets/dialogs/help.rs +++ b/src/app/widgets/dialogs/help.rs @@ -34,7 +34,7 @@ pub struct HelpDialog { gg_manager: MultiKey, /// A jury-rigged solution for shortcut indices. - /// TODO: THIS DOES NOT SCALE WELL! + /// TODO: [Refactor] Shortcut indices system - THIS DOES NOT SCALE WELL IN THE FUTURE! Write a better system like multikey (but for multiple combos). shortcut_indices: FxHashMap, } diff --git a/src/bin/main.rs b/src/bin/main.rs index 84b88d0b..c251b388 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -54,7 +54,7 @@ fn main() -> Result<()> { let input_thread = create_input_thread(sender.clone(), thread_termination_lock.clone()); // Cleaning loop - // TODO: Probably worth spinning this off into an async thread or something... + // TODO: [Refactor, Optimization (Potentially, maybe not)] Probably worth spinning this off into an async thread or something... let _cleaning_thread = { let lock = thread_termination_lock.clone(); let cvar = thread_termination_cvar.clone(); @@ -80,7 +80,7 @@ fn main() -> Result<()> { }; // Event loop - // TODO: Add back collection sender + // TODO: [Threads, Refactor, Config] Add back collection sender for config later if we need to change settings on the fly let (_collection_sender, collection_thread_ctrl_receiver) = mpsc::channel(); let _collection_thread = create_collection_thread( sender, @@ -102,7 +102,7 @@ fn main() -> Result<()> { terminal.hide_cursor()?; // Set panic hook - // TODO: Make this close all the child threads too! + // TODO: [Threads, Panic] Make this close all the child threads too! panic::set_hook(Box::new(|info| panic_hook(info))); // Set termination hook diff --git a/src/canvas.rs b/src/canvas.rs index 1adc3965..d95c6c4b 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -193,7 +193,7 @@ impl Painter { let middle_dialog_chunk = Layout::default() .direction(Direction::Horizontal) .constraints(if terminal_width < 100 { - // TODO: [REFACTOR] The point we start changing size at currently hard-coded in. + // TODO: [Drawing, Hard-coded] The point we start changing size at currently hard-coded in. [ Constraint::Percentage(0), Constraint::Percentage(100), @@ -210,7 +210,8 @@ impl Painter { help_dialog.draw_help(&self, f, middle_dialog_chunk[1]); } else if app_state.delete_dialog_state.is_showing_dd { - // TODO: This needs the paragraph wrap feature from tui-rs to be pushed to complete... but for now it's pretty close! + // TODO: [Drawing] Better dd sizing needs the paragraph wrap feature from tui-rs to be pushed to + // complete... but for now it's pretty close! // The main problem right now is that I cannot properly calculate the height offset since // line-wrapping is NOT the same as taking the width of the text and dividing by width. // So, I need the height AFTER wrapping. diff --git a/src/canvas/dialogs/dd_dialog.rs b/src/canvas/dialogs/dd_dialog.rs index ab5222ea..96612197 100644 --- a/src/canvas/dialogs/dd_dialog.rs +++ b/src/canvas/dialogs/dd_dialog.rs @@ -38,31 +38,31 @@ impl KillDialog for Painter { Spans::from(dd_err.clone()), Spans::from("Please press ENTER or ESC to close this dialog."), ])); - } else if let Some(to_kill_processes) = app_state.get_to_delete_processes() { - if let Some(first_pid) = to_kill_processes.1.first() { - return Some(Text::from(vec![ - Spans::from(""), - if app_state.is_grouped(app_state.current_widget.widget_id) { - if to_kill_processes.1.len() != 1 { - Spans::from(format!( - "Kill {} processes with the name \"{}\"? Press ENTER to confirm.", - to_kill_processes.1.len(), - to_kill_processes.0 - )) - } else { - Spans::from(format!( - "Kill 1 process with the name \"{}\"? Press ENTER to confirm.", - to_kill_processes.0 - )) - } - } else { - Spans::from(format!( - "Kill process \"{}\" with PID {}? Press ENTER to confirm.", - to_kill_processes.0, first_pid - )) - }, - ])); - } + } else if let Some(_to_kill_processes) = app_state.get_to_delete_processes() { + // if let Some(first_pid) = to_kill_processes.1.first() { + // return Some(Text::from(vec![ + // Spans::from(""), + // if app_state.is_grouped(app_state.current_widget.widget_id) { + // if to_kill_processes.1.len() != 1 { + // Spans::from(format!( + // "Kill {} processes with the name \"{}\"? Press ENTER to confirm.", + // to_kill_processes.1.len(), + // to_kill_processes.0 + // )) + // } else { + // Spans::from(format!( + // "Kill 1 process with the name \"{}\"? Press ENTER to confirm.", + // to_kill_processes.0 + // )) + // } + // } else { + // Spans::from(format!( + // "Kill process \"{}\" with PID {}? Press ENTER to confirm.", + // to_kill_processes.0, first_pid + // )) + // }, + // ])); + // } } None @@ -140,7 +140,7 @@ impl KillDialog for Painter { } else { #[cfg(target_family = "unix")] { - // TODO: Can probably make this const. + // TODO: [Optimization, Const] Can probably make this const. let signal_text; #[cfg(target_os = "linux")] { diff --git a/src/canvas/widgets/mem_graph.rs b/src/canvas/widgets/mem_graph.rs deleted file mode 100644 index b60fcdcb..00000000 --- a/src/canvas/widgets/mem_graph.rs +++ /dev/null @@ -1,249 +0,0 @@ -use crate::{ - app::App, - canvas::{drawing_utils::interpolate_points, Painter}, - constants::*, -}; - -use tui::{ - backend::Backend, - layout::{Constraint, Rect}, - symbols::Marker, - terminal::Frame, - text::Span, - text::Spans, - widgets::{Axis, Block, Borders, Chart, Dataset}, -}; -use unicode_segmentation::UnicodeSegmentation; - -pub trait MemGraphWidget { - fn draw_memory_graph( - &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64, - ); -} - -impl MemGraphWidget for Painter { - fn draw_memory_graph( - &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64, - ) { - if let Some(mem_widget_state) = app_state.mem_state.widget_states.get_mut(&widget_id) { - let mem_data: &mut [(f64, f64)] = &mut app_state.canvas_data.mem_data; - let swap_data: &mut [(f64, f64)] = &mut app_state.canvas_data.swap_data; - - let time_start = -(mem_widget_state.current_display_time as f64); - - let display_time_labels = vec![ - Span::styled( - format!("{}s", mem_widget_state.current_display_time / 1000), - self.colours.graph_style, - ), - Span::styled("0s".to_string(), self.colours.graph_style), - ]; - let y_axis_label = vec![ - Span::styled(" 0%", self.colours.graph_style), - Span::styled("100%", self.colours.graph_style), - ]; - - let x_axis = if app_state.app_config_fields.hide_time - || (app_state.app_config_fields.autohide_time - && mem_widget_state.autohide_timer.is_none()) - { - Axis::default().bounds([time_start, 0.0]) - } else if let Some(time) = mem_widget_state.autohide_timer { - if std::time::Instant::now().duration_since(time).as_millis() - < AUTOHIDE_TIMEOUT_MILLISECONDS as u128 - { - Axis::default() - .bounds([time_start, 0.0]) - .style(self.colours.graph_style) - .labels(display_time_labels) - } else { - mem_widget_state.autohide_timer = None; - Axis::default().bounds([time_start, 0.0]) - } - } else if draw_loc.height < TIME_LABEL_HEIGHT_LIMIT { - Axis::default().bounds([time_start, 0.0]) - } else { - Axis::default() - .bounds([time_start, 0.0]) - .style(self.colours.graph_style) - .labels(display_time_labels) - }; - - let y_axis = Axis::default() - .style(self.colours.graph_style) - .bounds([0.0, 100.5]) - .labels(y_axis_label); - - // Interpolate values to avoid ugly gaps - let interpolated_mem_point = if let Some(end_pos) = mem_data - .iter() - .position(|(time, _data)| *time >= time_start) - { - if end_pos > 1 { - let start_pos = end_pos - 1; - let outside_point = mem_data.get(start_pos); - let inside_point = mem_data.get(end_pos); - - if let (Some(outside_point), Some(inside_point)) = (outside_point, inside_point) - { - let old = *outside_point; - - let new_point = ( - time_start, - interpolate_points(outside_point, inside_point, time_start), - ); - - if let Some(to_replace) = mem_data.get_mut(start_pos) { - *to_replace = new_point; - Some((start_pos, old)) - } else { - None // Failed to get mutable reference. - } - } else { - None // Point somehow doesn't exist in our data - } - } else { - None // Point is already "leftmost", no need to interpolate. - } - } else { - None // There is no point. - }; - - let interpolated_swap_point = if let Some(end_pos) = swap_data - .iter() - .position(|(time, _data)| *time >= time_start) - { - if end_pos > 1 { - let start_pos = end_pos - 1; - let outside_point = swap_data.get(start_pos); - let inside_point = swap_data.get(end_pos); - - if let (Some(outside_point), Some(inside_point)) = (outside_point, inside_point) - { - let old = *outside_point; - - let new_point = ( - time_start, - interpolate_points(outside_point, inside_point, time_start), - ); - - if let Some(to_replace) = swap_data.get_mut(start_pos) { - *to_replace = new_point; - Some((start_pos, old)) - } else { - None // Failed to get mutable reference. - } - } else { - None // Point somehow doesn't exist in our data - } - } else { - None // Point is already "leftmost", no need to interpolate. - } - } else { - None // There is no point. - }; - - let mut mem_canvas_vec: Vec> = vec![]; - - if let Some((label_percent, label_frac)) = &app_state.canvas_data.mem_labels { - let mem_label = format!("RAM:{}{}", label_percent, label_frac); - mem_canvas_vec.push( - Dataset::default() - .name(mem_label) - .marker(if app_state.app_config_fields.use_dot { - Marker::Dot - } else { - Marker::Braille - }) - .style(self.colours.ram_style) - .data(mem_data) - .graph_type(tui::widgets::GraphType::Line), - ); - } - - if let Some((label_percent, label_frac)) = &app_state.canvas_data.swap_labels { - let swap_label = format!("SWP:{}{}", label_percent, label_frac); - mem_canvas_vec.push( - Dataset::default() - .name(swap_label) - .marker(if app_state.app_config_fields.use_dot { - Marker::Dot - } else { - Marker::Braille - }) - .style(self.colours.swap_style) - .data(swap_data) - .graph_type(tui::widgets::GraphType::Line), - ); - } - - let is_on_widget = widget_id == app_state.current_widget.widget_id; - let border_style = if is_on_widget { - self.colours.highlighted_border_style - } else { - self.colours.border_style - }; - - let title = if app_state.is_expanded { - const TITLE_BASE: &str = " Memory ── Esc to go back "; - Spans::from(vec![ - Span::styled(" Memory ", self.colours.widget_title_style), - Span::styled( - format!( - "─{}─ Esc to go back ", - "─".repeat(usize::from(draw_loc.width).saturating_sub( - UnicodeSegmentation::graphemes(TITLE_BASE, true).count() + 2 - )) - ), - border_style, - ), - ]) - } else { - Spans::from(Span::styled( - " Memory ".to_string(), - self.colours.widget_title_style, - )) - }; - - f.render_widget( - Chart::new(mem_canvas_vec) - .block( - Block::default() - .title(title) - .borders(Borders::ALL) - .border_style(if app_state.current_widget.widget_id == widget_id { - self.colours.highlighted_border_style - } else { - self.colours.border_style - }), - ) - .x_axis(x_axis) - .y_axis(y_axis) - .hidden_legend_constraints((Constraint::Ratio(3, 4), Constraint::Ratio(3, 4))), - draw_loc, - ); - - // Now if you're done, reset any interpolated points! - if let Some((index, old_value)) = interpolated_mem_point { - if let Some(to_replace) = mem_data.get_mut(index) { - *to_replace = old_value; - } - } - - if let Some((index, old_value)) = interpolated_swap_point { - if let Some(to_replace) = swap_data.get_mut(index) { - *to_replace = old_value; - } - } - } - - if app_state.should_get_widget_bounds() { - // Update draw loc in widget map - if let Some(widget) = app_state.widget_map.get_mut(&widget_id) { - widget.top_left_corner = Some((draw_loc.x, draw_loc.y)); - widget.bottom_right_corner = - Some((draw_loc.x + draw_loc.width, draw_loc.y + draw_loc.height)); - } - } - } -} diff --git a/src/canvas/widgets/network_graph.rs b/src/canvas/widgets/network_graph.rs deleted file mode 100644 index 8cdf9449..00000000 --- a/src/canvas/widgets/network_graph.rs +++ /dev/null @@ -1,770 +0,0 @@ -use once_cell::sync::Lazy; -use std::cmp::max; -use unicode_segmentation::UnicodeSegmentation; - -use crate::{ - app::{App, AxisScaling}, - canvas::{ - drawing_utils::{get_column_widths, interpolate_points}, - Painter, - }, - constants::*, - units::data_units::DataUnit, - utils::gen_util::*, -}; - -use tui::{ - backend::Backend, - layout::{Constraint, Direction, Layout, Rect}, - symbols::Marker, - terminal::Frame, - text::Span, - text::{Spans, Text}, - widgets::{Axis, Block, Borders, Chart, Dataset, Row, Table}, -}; - -const NETWORK_HEADERS: [&str; 4] = ["RX", "TX", "Total RX", "Total TX"]; - -static NETWORK_HEADERS_LENS: Lazy> = Lazy::new(|| { - NETWORK_HEADERS - .iter() - .map(|entry| entry.len() as u16) - .collect::>() -}); - -pub trait NetworkGraphWidget { - fn draw_network( - &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64, - ); - - fn draw_network_graph( - &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64, - hide_legend: bool, - ); - - fn draw_network_labels( - &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64, - ); -} - -impl NetworkGraphWidget for Painter { - fn draw_network( - &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64, - ) { - if app_state.app_config_fields.use_old_network_legend { - let network_chunk = Layout::default() - .direction(Direction::Vertical) - .margin(0) - .constraints([ - Constraint::Length(max(draw_loc.height as i64 - 5, 0) as u16), - Constraint::Length(5), - ]) - .split(draw_loc); - - self.draw_network_graph(f, app_state, network_chunk[0], widget_id, true); - self.draw_network_labels(f, app_state, network_chunk[1], widget_id); - } else { - self.draw_network_graph(f, app_state, draw_loc, widget_id, false); - } - - if app_state.should_get_widget_bounds() { - // Update draw loc in widget map - // Note that in both cases, we always go to the same widget id so it's fine to do it like - // this lol. - if let Some(network_widget) = app_state.widget_map.get_mut(&widget_id) { - network_widget.top_left_corner = Some((draw_loc.x, draw_loc.y)); - network_widget.bottom_right_corner = - Some((draw_loc.x + draw_loc.width, draw_loc.y + draw_loc.height)); - } - } - } - - fn draw_network_graph( - &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64, - hide_legend: bool, - ) { - /// Point is of time, data - type Point = (f64, f64); - - /// Returns the max data point and time given a time. - fn get_max_entry( - rx: &[Point], tx: &[Point], time_start: f64, network_scale_type: &AxisScaling, - network_use_binary_prefix: bool, - ) -> (f64, f64) { - /// Determines a "fake" max value in circumstances where we couldn't find one from the data. - fn calculate_missing_max( - network_scale_type: &AxisScaling, network_use_binary_prefix: bool, - ) -> f64 { - match network_scale_type { - AxisScaling::Log => { - if network_use_binary_prefix { - LOG_KIBI_LIMIT - } else { - LOG_KILO_LIMIT - } - } - AxisScaling::Linear => { - if network_use_binary_prefix { - KIBI_LIMIT_F64 - } else { - KILO_LIMIT_F64 - } - } - } - } - - // First, let's shorten our ranges to actually look. We can abuse the fact that our rx and tx arrays - // are sorted, so we can short-circuit our search to filter out only the relevant data points... - let filtered_rx = if let (Some(rx_start), Some(rx_end)) = ( - rx.iter().position(|(time, _data)| *time >= time_start), - rx.iter().rposition(|(time, _data)| *time <= 0.0), - ) { - Some(&rx[rx_start..=rx_end]) - } else { - None - }; - - let filtered_tx = if let (Some(tx_start), Some(tx_end)) = ( - tx.iter().position(|(time, _data)| *time >= time_start), - tx.iter().rposition(|(time, _data)| *time <= 0.0), - ) { - Some(&tx[tx_start..=tx_end]) - } else { - None - }; - - // Then, find the maximal rx/tx so we know how to scale, and return it. - match (filtered_rx, filtered_tx) { - (None, None) => ( - time_start, - calculate_missing_max(network_scale_type, network_use_binary_prefix), - ), - (None, Some(filtered_tx)) => { - match filtered_tx - .iter() - .max_by(|(_, data_a), (_, data_b)| get_ordering(data_a, data_b, false)) - { - Some((best_time, max_val)) => { - if *max_val == 0.0 { - ( - time_start, - calculate_missing_max( - network_scale_type, - network_use_binary_prefix, - ), - ) - } else { - (*best_time, *max_val) - } - } - None => ( - time_start, - calculate_missing_max(network_scale_type, network_use_binary_prefix), - ), - } - } - (Some(filtered_rx), None) => { - match filtered_rx - .iter() - .max_by(|(_, data_a), (_, data_b)| get_ordering(data_a, data_b, false)) - { - Some((best_time, max_val)) => { - if *max_val == 0.0 { - ( - time_start, - calculate_missing_max( - network_scale_type, - network_use_binary_prefix, - ), - ) - } else { - (*best_time, *max_val) - } - } - None => ( - time_start, - calculate_missing_max(network_scale_type, network_use_binary_prefix), - ), - } - } - (Some(filtered_rx), Some(filtered_tx)) => { - match filtered_rx - .iter() - .chain(filtered_tx) - .max_by(|(_, data_a), (_, data_b)| get_ordering(data_a, data_b, false)) - { - Some((best_time, max_val)) => { - if *max_val == 0.0 { - ( - *best_time, - calculate_missing_max( - network_scale_type, - network_use_binary_prefix, - ), - ) - } else { - (*best_time, *max_val) - } - } - None => ( - time_start, - calculate_missing_max(network_scale_type, network_use_binary_prefix), - ), - } - } - } - } - - /// Returns the required max data point and labels. - fn adjust_network_data_point( - max_entry: f64, network_scale_type: &AxisScaling, network_unit_type: &DataUnit, - network_use_binary_prefix: bool, - ) -> (f64, Vec) { - // So, we're going with an approach like this for linear data: - // - Main goal is to maximize the amount of information displayed given a specific height. - // We don't want to drown out some data if the ranges are too far though! Nor do we want to filter - // out too much data... - // - Change the y-axis unit (kilo/kibi, mega/mebi...) dynamically based on max load. - // - // The idea is we take the top value, build our scale such that each "point" is a scaled version of that. - // So for example, let's say I use 390 Mb/s. If I drew 4 segments, it would be 97.5, 195, 292.5, 390, and - // probably something like 438.75? - // - // So, how do we do this in tui-rs? Well, if we are using intervals that tie in perfectly to the max - // value we want... then it's actually not that hard. Since tui-rs accepts a vector as labels and will - // properly space them all out... we just work with that and space it out properly. - // - // Dynamic chart idea based off of FreeNAS's chart design. - // - // === - // - // For log data, we just use the old method of log intervals (kilo/mega/giga/etc.). Keep it nice and simple. - - // Now just check the largest unit we correspond to... then proceed to build some entries from there! - - let unit_char = match network_unit_type { - DataUnit::Byte => "B", - DataUnit::Bit => "b", - }; - - match network_scale_type { - AxisScaling::Linear => { - let (k_limit, m_limit, g_limit, t_limit) = if network_use_binary_prefix { - ( - KIBI_LIMIT_F64, - MEBI_LIMIT_F64, - GIBI_LIMIT_F64, - TEBI_LIMIT_F64, - ) - } else { - ( - KILO_LIMIT_F64, - MEGA_LIMIT_F64, - GIGA_LIMIT_F64, - TERA_LIMIT_F64, - ) - }; - - let bumped_max_entry = max_entry * 1.5; // We use the bumped up version to calculate our unit type. - let (max_value_scaled, unit_prefix, unit_type): (f64, &str, &str) = - if bumped_max_entry < k_limit { - (max_entry, "", unit_char) - } else if bumped_max_entry < m_limit { - ( - max_entry / k_limit, - if network_use_binary_prefix { "Ki" } else { "K" }, - unit_char, - ) - } else if bumped_max_entry < g_limit { - ( - max_entry / m_limit, - if network_use_binary_prefix { "Mi" } else { "M" }, - unit_char, - ) - } else if bumped_max_entry < t_limit { - ( - max_entry / g_limit, - if network_use_binary_prefix { "Gi" } else { "G" }, - unit_char, - ) - } else { - ( - max_entry / t_limit, - if network_use_binary_prefix { "Ti" } else { "T" }, - unit_char, - ) - }; - - // Finally, build an acceptable range starting from there, using the given height! - // Note we try to put more of a weight on the bottom section vs. the top, since the top has less data. - - let base_unit = max_value_scaled; - let labels: Vec = vec![ - format!("0{}{}", unit_prefix, unit_type), - format!("{:.1}", base_unit * 0.5), - format!("{:.1}", base_unit), - format!("{:.1}", base_unit * 1.5), - ] - .into_iter() - .map(|s| format!("{:>5}", s)) // Pull 5 as the longest legend value is generally going to be 5 digits (if they somehow hit over 5 terabits per second) - .collect(); - - (bumped_max_entry, labels) - } - AxisScaling::Log => { - let (m_limit, g_limit, t_limit) = if network_use_binary_prefix { - (LOG_MEBI_LIMIT, LOG_GIBI_LIMIT, LOG_TEBI_LIMIT) - } else { - (LOG_MEGA_LIMIT, LOG_GIGA_LIMIT, LOG_TERA_LIMIT) - }; - - fn get_zero(network_use_binary_prefix: bool, unit_char: &str) -> String { - format!( - "{}0{}", - if network_use_binary_prefix { " " } else { " " }, - unit_char - ) - } - - fn get_k(network_use_binary_prefix: bool, unit_char: &str) -> String { - format!( - "1{}{}", - if network_use_binary_prefix { "Ki" } else { "K" }, - unit_char - ) - } - - fn get_m(network_use_binary_prefix: bool, unit_char: &str) -> String { - format!( - "1{}{}", - if network_use_binary_prefix { "Mi" } else { "M" }, - unit_char - ) - } - - fn get_g(network_use_binary_prefix: bool, unit_char: &str) -> String { - format!( - "1{}{}", - if network_use_binary_prefix { "Gi" } else { "G" }, - unit_char - ) - } - - fn get_t(network_use_binary_prefix: bool, unit_char: &str) -> String { - format!( - "1{}{}", - if network_use_binary_prefix { "Ti" } else { "T" }, - unit_char - ) - } - - fn get_p(network_use_binary_prefix: bool, unit_char: &str) -> String { - format!( - "1{}{}", - if network_use_binary_prefix { "Pi" } else { "P" }, - unit_char - ) - } - - if max_entry < m_limit { - ( - m_limit, - vec![ - get_zero(network_use_binary_prefix, unit_char), - get_k(network_use_binary_prefix, unit_char), - get_m(network_use_binary_prefix, unit_char), - ], - ) - } else if max_entry < g_limit { - ( - g_limit, - vec![ - get_zero(network_use_binary_prefix, unit_char), - get_k(network_use_binary_prefix, unit_char), - get_m(network_use_binary_prefix, unit_char), - get_g(network_use_binary_prefix, unit_char), - ], - ) - } else if max_entry < t_limit { - ( - t_limit, - vec![ - get_zero(network_use_binary_prefix, unit_char), - get_k(network_use_binary_prefix, unit_char), - get_m(network_use_binary_prefix, unit_char), - get_g(network_use_binary_prefix, unit_char), - get_t(network_use_binary_prefix, unit_char), - ], - ) - } else { - // I really doubt anyone's transferring beyond petabyte speeds... - ( - if network_use_binary_prefix { - LOG_PEBI_LIMIT - } else { - LOG_PETA_LIMIT - }, - vec![ - get_zero(network_use_binary_prefix, unit_char), - get_k(network_use_binary_prefix, unit_char), - get_m(network_use_binary_prefix, unit_char), - get_g(network_use_binary_prefix, unit_char), - get_t(network_use_binary_prefix, unit_char), - get_p(network_use_binary_prefix, unit_char), - ], - ) - } - } - } - } - - if let Some(network_widget_state) = app_state.net_state.widget_states.get_mut(&widget_id) { - let network_data_rx: &mut [(f64, f64)] = &mut app_state.canvas_data.network_data_rx; - let network_data_tx: &mut [(f64, f64)] = &mut app_state.canvas_data.network_data_tx; - - let time_start = -(network_widget_state.current_display_time as f64); - - let display_time_labels = vec![ - Span::styled( - format!("{}s", network_widget_state.current_display_time / 1000), - self.colours.graph_style, - ), - Span::styled("0s".to_string(), self.colours.graph_style), - ]; - let x_axis = if app_state.app_config_fields.hide_time - || (app_state.app_config_fields.autohide_time - && network_widget_state.autohide_timer.is_none()) - { - Axis::default().bounds([time_start, 0.0]) - } else if let Some(time) = network_widget_state.autohide_timer { - if std::time::Instant::now().duration_since(time).as_millis() - < AUTOHIDE_TIMEOUT_MILLISECONDS as u128 - { - Axis::default() - .bounds([time_start, 0.0]) - .style(self.colours.graph_style) - .labels(display_time_labels) - } else { - network_widget_state.autohide_timer = None; - Axis::default().bounds([time_start, 0.0]) - } - } else if draw_loc.height < TIME_LABEL_HEIGHT_LIMIT { - Axis::default().bounds([time_start, 0.0]) - } else { - Axis::default() - .bounds([time_start, 0.0]) - .style(self.colours.graph_style) - .labels(display_time_labels) - }; - - // Interpolate a point for rx and tx between the last value outside of the left bounds and the first value - // inside it. - // Because we assume it is all in order for... basically all our code, we can't just append it, - // and insertion in the middle seems. So instead, we swap *out* the value that is outside with our - // interpolated point, draw and do whatever calculations, then swap back in the old value! - // - // Note there is some re-used work here! For potential optimizations, we could re-use some work here in/from - // get_max_entry... - let interpolated_rx_point = if let Some(rx_end_pos) = network_data_rx - .iter() - .position(|(time, _data)| *time >= time_start) - { - if rx_end_pos > 1 { - let rx_start_pos = rx_end_pos - 1; - let outside_rx_point = network_data_rx.get(rx_start_pos); - let inside_rx_point = network_data_rx.get(rx_end_pos); - - if let (Some(outside_rx_point), Some(inside_rx_point)) = - (outside_rx_point, inside_rx_point) - { - let old = *outside_rx_point; - - let new_point = ( - time_start, - interpolate_points(outside_rx_point, inside_rx_point, time_start), - ); - - // debug!( - // "Interpolated between {:?} and {:?}, got rx for time {:?}: {:?}", - // outside_rx_point, inside_rx_point, time_start, new_point - // ); - - if let Some(to_replace) = network_data_rx.get_mut(rx_start_pos) { - *to_replace = new_point; - Some((rx_start_pos, old)) - } else { - None // Failed to get mutable reference. - } - } else { - None // Point somehow doesn't exist in our network_data_rx - } - } else { - None // Point is already "leftmost", no need to interpolate. - } - } else { - None // There is no point. - }; - - let interpolated_tx_point = if let Some(tx_end_pos) = network_data_tx - .iter() - .position(|(time, _data)| *time >= time_start) - { - if tx_end_pos > 1 { - let tx_start_pos = tx_end_pos - 1; - let outside_tx_point = network_data_tx.get(tx_start_pos); - let inside_tx_point = network_data_tx.get(tx_end_pos); - - if let (Some(outside_tx_point), Some(inside_tx_point)) = - (outside_tx_point, inside_tx_point) - { - let old = *outside_tx_point; - - let new_point = ( - time_start, - interpolate_points(outside_tx_point, inside_tx_point, time_start), - ); - - if let Some(to_replace) = network_data_tx.get_mut(tx_start_pos) { - *to_replace = new_point; - Some((tx_start_pos, old)) - } else { - None // Failed to get mutable reference. - } - } else { - None // Point somehow doesn't exist in our network_data_tx - } - } else { - None // Point is already "leftmost", no need to interpolate. - } - } else { - None // There is no point. - }; - - // TODO: Cache network results: Only update if: - // - Force update (includes time interval change) - // - Old max time is off screen - // - A new time interval is better and does not fit (check from end of vector to last checked; we only want to update if it is TOO big!) - - // Find the maximal rx/tx so we know how to scale, and return it. - - let (_best_time, max_entry) = get_max_entry( - network_data_rx, - network_data_tx, - time_start, - &app_state.app_config_fields.network_scale_type, - app_state.app_config_fields.network_use_binary_prefix, - ); - - let (max_range, labels) = adjust_network_data_point( - max_entry, - &app_state.app_config_fields.network_scale_type, - &app_state.app_config_fields.network_unit_type, - app_state.app_config_fields.network_use_binary_prefix, - ); - - // Cache results. - // network_widget_state.draw_max_range_cache = max_range; - // network_widget_state.draw_time_start_cache = best_time; - // network_widget_state.draw_labels_cache = labels; - - let y_axis_labels = labels - .iter() - .map(|label| Span::styled(label, self.colours.graph_style)) - .collect::>(); - let y_axis = Axis::default() - .style(self.colours.graph_style) - .bounds([0.0, max_range]) - .labels(y_axis_labels); - - let is_on_widget = widget_id == app_state.current_widget.widget_id; - let border_style = if is_on_widget { - self.colours.highlighted_border_style - } else { - self.colours.border_style - }; - - let title = if app_state.is_expanded { - const TITLE_BASE: &str = " Network ── Esc to go back "; - Spans::from(vec![ - Span::styled(" Network ", self.colours.widget_title_style), - Span::styled( - format!( - "─{}─ Esc to go back ", - "─".repeat(usize::from(draw_loc.width).saturating_sub( - UnicodeSegmentation::graphemes(TITLE_BASE, true).count() + 2 - )) - ), - border_style, - ), - ]) - } else { - Spans::from(Span::styled(" Network ", self.colours.widget_title_style)) - }; - - let legend_constraints = if hide_legend { - (Constraint::Ratio(0, 1), Constraint::Ratio(0, 1)) - } else { - (Constraint::Ratio(1, 1), Constraint::Ratio(3, 4)) - }; - - // TODO: Add support for clicking on legend to only show that value on chart. - let dataset = if app_state.app_config_fields.use_old_network_legend && !hide_legend { - vec![ - Dataset::default() - .name(format!("RX: {:7}", app_state.canvas_data.rx_display)) - .marker(if app_state.app_config_fields.use_dot { - Marker::Dot - } else { - Marker::Braille - }) - .style(self.colours.rx_style) - .data(network_data_rx) - .graph_type(tui::widgets::GraphType::Line), - Dataset::default() - .name(format!("TX: {:7}", app_state.canvas_data.tx_display)) - .marker(if app_state.app_config_fields.use_dot { - Marker::Dot - } else { - Marker::Braille - }) - .style(self.colours.tx_style) - .data(network_data_tx) - .graph_type(tui::widgets::GraphType::Line), - Dataset::default() - .name(format!( - "Total RX: {:7}", - app_state.canvas_data.total_rx_display - )) - .style(self.colours.total_rx_style), - Dataset::default() - .name(format!( - "Total TX: {:7}", - app_state.canvas_data.total_tx_display - )) - .style(self.colours.total_tx_style), - ] - } else { - vec![ - Dataset::default() - .name(&app_state.canvas_data.rx_display) - .marker(if app_state.app_config_fields.use_dot { - Marker::Dot - } else { - Marker::Braille - }) - .style(self.colours.rx_style) - .data(network_data_rx) - .graph_type(tui::widgets::GraphType::Line), - Dataset::default() - .name(&app_state.canvas_data.tx_display) - .marker(if app_state.app_config_fields.use_dot { - Marker::Dot - } else { - Marker::Braille - }) - .style(self.colours.tx_style) - .data(network_data_tx) - .graph_type(tui::widgets::GraphType::Line), - ] - }; - - f.render_widget( - Chart::new(dataset) - .block( - Block::default() - .title(title) - .borders(Borders::ALL) - .border_style(if app_state.current_widget.widget_id == widget_id { - self.colours.highlighted_border_style - } else { - self.colours.border_style - }), - ) - .x_axis(x_axis) - .y_axis(y_axis) - .hidden_legend_constraints(legend_constraints), - draw_loc, - ); - - // Now if you're done, reset any interpolated points! - if let Some((index, old_value)) = interpolated_rx_point { - if let Some(to_replace) = network_data_rx.get_mut(index) { - *to_replace = old_value; - } - } - - if let Some((index, old_value)) = interpolated_tx_point { - if let Some(to_replace) = network_data_tx.get_mut(index) { - *to_replace = old_value; - } - } - } - } - - fn draw_network_labels( - &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64, - ) { - let table_gap = if draw_loc.height < TABLE_GAP_HEIGHT_LIMIT { - 0 - } else { - app_state.app_config_fields.table_gap - }; - - let rx_display = &app_state.canvas_data.rx_display; - let tx_display = &app_state.canvas_data.tx_display; - let total_rx_display = &app_state.canvas_data.total_rx_display; - let total_tx_display = &app_state.canvas_data.total_tx_display; - - // Gross but I need it to work... - let total_network = vec![vec![ - Text::raw(rx_display), - Text::raw(tx_display), - Text::raw(total_rx_display), - Text::raw(total_tx_display), - ]]; - let mapped_network = total_network - .into_iter() - .map(|val| Row::new(val).style(self.colours.text_style)); - - // Calculate widths - let intrinsic_widths = get_column_widths( - draw_loc.width, - &[None, None, None, None], - &(NETWORK_HEADERS_LENS - .iter() - .map(|s| Some(*s)) - .collect::>()), - &[Some(0.25); 4], - &(NETWORK_HEADERS_LENS - .iter() - .map(|s| Some(*s)) - .collect::>()), - true, - ); - - // Draw - f.render_widget( - Table::new(mapped_network) - .header( - Row::new(NETWORK_HEADERS.to_vec()) - .style(self.colours.table_header_style) - .bottom_margin(table_gap), - ) - .block(Block::default().borders(Borders::ALL).border_style( - if app_state.current_widget.widget_id == widget_id { - self.colours.highlighted_border_style - } else { - self.colours.border_style - }, - )) - .style(self.colours.text_style) - .widths( - &(intrinsic_widths - .iter() - .map(|calculated_width| Constraint::Length(*calculated_width as u16)) - .collect::>()), - ), - draw_loc, - ); - } -} diff --git a/src/canvas/widgets/process_table.rs b/src/canvas/widgets/process_table.rs deleted file mode 100644 index 0a87ae3e..00000000 --- a/src/canvas/widgets/process_table.rs +++ /dev/null @@ -1,911 +0,0 @@ -use crate::{ - app::App, - canvas::{ - drawing_utils::{get_column_widths, get_search_start_position, get_start_position}, - Painter, - }, - constants::*, -}; - -use tui::{ - backend::Backend, - layout::{Alignment, Constraint, Direction, Layout, Rect}, - terminal::Frame, - text::{Span, Spans, Text}, - widgets::{Block, Borders, Paragraph, Row, Table}, -}; - -use unicode_segmentation::{GraphemeIndices, UnicodeSegmentation}; -use unicode_width::UnicodeWidthStr; - -use once_cell::sync::Lazy; - -static PROCESS_HEADERS_HARD_WIDTH_NO_GROUP: Lazy>> = Lazy::new(|| { - vec![ - Some(7), - None, - Some(8), - Some(8), - Some(8), - Some(8), - Some(7), - Some(8), - #[cfg(target_family = "unix")] - None, - None, - ] -}); -static PROCESS_HEADERS_HARD_WIDTH_GROUPED: Lazy>> = Lazy::new(|| { - vec![ - Some(7), - None, - Some(8), - Some(8), - Some(8), - Some(8), - Some(7), - Some(8), - ] -}); - -static PROCESS_HEADERS_SOFT_WIDTH_MAX_GROUPED_COMMAND: Lazy>> = - Lazy::new(|| vec![None, Some(0.7), None, None, None, None, None, None]); -static PROCESS_HEADERS_SOFT_WIDTH_MAX_GROUPED_ELSE: Lazy>> = - Lazy::new(|| vec![None, Some(0.3), None, None, None, None, None, None]); - -static PROCESS_HEADERS_SOFT_WIDTH_MAX_NO_GROUP_COMMAND: Lazy>> = Lazy::new(|| { - vec![ - None, - Some(0.7), - None, - None, - None, - None, - None, - None, - #[cfg(target_family = "unix")] - Some(0.05), - Some(0.2), - ] -}); -static PROCESS_HEADERS_SOFT_WIDTH_MAX_NO_GROUP_TREE: Lazy>> = Lazy::new(|| { - vec![ - None, - Some(0.5), - None, - None, - None, - None, - None, - None, - #[cfg(target_family = "unix")] - Some(0.05), - Some(0.2), - ] -}); -static PROCESS_HEADERS_SOFT_WIDTH_MAX_NO_GROUP_ELSE: Lazy>> = Lazy::new(|| { - vec![ - None, - Some(0.3), - None, - None, - None, - None, - None, - None, - #[cfg(target_family = "unix")] - Some(0.05), - Some(0.2), - ] -}); - -pub trait ProcessTableWidget { - /// Draws and handles all process-related drawing. Use this. - /// - `widget_id` here represents the widget ID of the process widget itself! - fn draw_process_features( - &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool, - widget_id: u64, - ); - - /// Draws the process sort box. - /// - `widget_id` represents the widget ID of the process widget itself. - /// - /// This should not be directly called. - fn draw_processes_table( - &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool, - widget_id: u64, - ); - - /// Draws the process search field. - /// - `widget_id` represents the widget ID of the search box itself --- NOT the process widget - /// state that is stored. - /// - /// This should not be directly called. - fn draw_search_field( - &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool, - widget_id: u64, - ); - - /// Draws the process sort box. - /// - `widget_id` represents the widget ID of the sort box itself --- NOT the process widget - /// state that is stored. - /// - /// This should not be directly called. - fn draw_process_sort( - &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool, - widget_id: u64, - ); -} - -impl ProcessTableWidget for Painter { - fn draw_process_features( - &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool, - widget_id: u64, - ) { - if let Some(process_widget_state) = app_state.proc_state.widget_states.get(&widget_id) { - let search_height = if draw_border { 5 } else { 3 }; - let is_sort_open = process_widget_state.is_sort_open; - let header_len = process_widget_state.columns.longest_header_len; - - let mut proc_draw_loc = draw_loc; - if process_widget_state.is_search_enabled() { - let processes_chunk = Layout::default() - .direction(Direction::Vertical) - .constraints([Constraint::Min(0), Constraint::Length(search_height)]) - .split(draw_loc); - proc_draw_loc = processes_chunk[0]; - - self.draw_search_field( - f, - app_state, - processes_chunk[1], - draw_border, - widget_id + 1, - ); - } - - if is_sort_open { - let processes_chunk = Layout::default() - .direction(Direction::Horizontal) - .constraints([Constraint::Length(header_len + 4), Constraint::Min(0)]) - .split(proc_draw_loc); - proc_draw_loc = processes_chunk[1]; - - self.draw_process_sort( - f, - app_state, - processes_chunk[0], - draw_border, - widget_id + 2, - ); - } - - self.draw_processes_table(f, app_state, proc_draw_loc, draw_border, widget_id); - } - } - - fn draw_processes_table( - &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool, - widget_id: u64, - ) { - let should_get_widget_bounds = app_state.should_get_widget_bounds(); - if let Some(proc_widget_state) = app_state.proc_state.widget_states.get_mut(&widget_id) { - let recalculate_column_widths = - should_get_widget_bounds || proc_widget_state.requires_redraw; - if proc_widget_state.requires_redraw { - proc_widget_state.requires_redraw = false; - } - - let is_on_widget = widget_id == app_state.current_widget.widget_id; - let margined_draw_loc = Layout::default() - .constraints([Constraint::Percentage(100)]) - .horizontal_margin(if is_on_widget || draw_border { 0 } else { 1 }) - .direction(Direction::Horizontal) - .split(draw_loc)[0]; - - let (border_style, highlight_style) = if is_on_widget { - ( - self.colours.highlighted_border_style, - self.colours.currently_selected_text_style, - ) - } else { - (self.colours.border_style, self.colours.text_style) - }; - - let title_base = if app_state.app_config_fields.show_table_scroll_position { - if let Some(finalized_process_data) = app_state - .canvas_data - .finalized_process_data_map - .get(&widget_id) - { - let title = format!( - " Processes ({} of {}) ", - proc_widget_state - .scroll_state - .current_scroll_position - .saturating_add(1), - finalized_process_data.len() - ); - - if title.len() <= draw_loc.width as usize { - title - } else { - " Processes ".to_string() - } - } else { - " Processes ".to_string() - } - } else { - " Processes ".to_string() - }; - - let title = if app_state.is_expanded - && !proc_widget_state - .process_search_state - .search_state - .is_enabled - && !proc_widget_state.is_sort_open - { - const ESCAPE_ENDING: &str = "── Esc to go back "; - - let (chosen_title_base, expanded_title_base) = { - let temp_title_base = format!("{}{}", title_base, ESCAPE_ENDING); - - if temp_title_base.len() > draw_loc.width as usize { - ( - " Processes ".to_string(), - format!("{}{}", " Processes ".to_string(), ESCAPE_ENDING), - ) - } else { - (title_base, temp_title_base) - } - }; - - Spans::from(vec![ - Span::styled(chosen_title_base, self.colours.widget_title_style), - Span::styled( - format!( - "─{}─ Esc to go back ", - "─".repeat( - usize::from(draw_loc.width).saturating_sub( - UnicodeSegmentation::graphemes( - expanded_title_base.as_str(), - true - ) - .count() - + 2 - ) - ) - ), - border_style, - ), - ]) - } else { - Spans::from(Span::styled(title_base, self.colours.widget_title_style)) - }; - - let process_block = if draw_border { - Block::default() - .title(title) - .borders(Borders::ALL) - .border_style(border_style) - } else if is_on_widget { - Block::default() - .borders(*SIDE_BORDERS) - .border_style(self.colours.highlighted_border_style) - } else { - Block::default().borders(Borders::NONE) - }; - - if let Some(process_data) = &app_state - .canvas_data - .stringified_process_data_map - .get(&widget_id) - { - let table_gap = if draw_loc.height < TABLE_GAP_HEIGHT_LIMIT { - 0 - } else { - app_state.app_config_fields.table_gap - }; - let position = get_start_position( - usize::from( - (draw_loc.height + (1 - table_gap)) - .saturating_sub(self.table_height_offset), - ), - &proc_widget_state.scroll_state.scroll_direction, - &mut proc_widget_state.scroll_state.previous_scroll_position, - proc_widget_state.scroll_state.current_scroll_position, - app_state.is_force_redraw, - ); - - // Sanity check - let start_position = if position >= process_data.len() { - process_data.len().saturating_sub(1) - } else { - position - }; - - let sliced_vec = &process_data[start_position..]; - let processed_sliced_vec = sliced_vec.iter().map(|(data, disabled)| { - ( - data.iter() - .map(|(entry, _alternative)| entry) - .collect::>(), - disabled, - ) - }); - - let proc_table_state = &mut proc_widget_state.scroll_state.table_state; - proc_table_state.select(Some( - proc_widget_state - .scroll_state - .current_scroll_position - .saturating_sub(start_position), - )); - - // Draw! - let process_headers = proc_widget_state.columns.get_column_headers( - &proc_widget_state.process_sorting_type, - proc_widget_state.is_process_sort_descending, - ); - - // Calculate widths - // FIXME: See if we can move this into the recalculate block? I want to move column widths into the column widths - let hard_widths = if proc_widget_state.is_grouped { - &*PROCESS_HEADERS_HARD_WIDTH_GROUPED - } else { - &*PROCESS_HEADERS_HARD_WIDTH_NO_GROUP - }; - - if recalculate_column_widths { - let mut column_widths = process_headers - .iter() - .map(|entry| UnicodeWidthStr::width(entry.as_str()) as u16) - .collect::>(); - - let soft_widths_min = column_widths - .iter() - .map(|width| Some(*width)) - .collect::>(); - - proc_widget_state.table_width_state.desired_column_widths = { - for (row, _disabled) in processed_sliced_vec.clone() { - for (col, entry) in row.iter().enumerate() { - if let Some(col_width) = column_widths.get_mut(col) { - let grapheme_len = UnicodeWidthStr::width(entry.as_str()); - if grapheme_len as u16 > *col_width { - *col_width = grapheme_len as u16; - } - } - } - } - column_widths - }; - - proc_widget_state.table_width_state.desired_column_widths = proc_widget_state - .table_width_state - .desired_column_widths - .iter() - .zip(hard_widths) - .map(|(current, hard)| { - if let Some(hard) = hard { - if *hard > *current { - *hard - } else { - *current - } - } else { - *current - } - }) - .collect::>(); - - let soft_widths_max = if proc_widget_state.is_grouped { - // Note grouped trees are not a thing. - - if proc_widget_state.is_using_command { - &*PROCESS_HEADERS_SOFT_WIDTH_MAX_GROUPED_COMMAND - } else { - &*PROCESS_HEADERS_SOFT_WIDTH_MAX_GROUPED_ELSE - } - } else if proc_widget_state.is_using_command { - &*PROCESS_HEADERS_SOFT_WIDTH_MAX_NO_GROUP_COMMAND - } else if proc_widget_state.is_tree_mode { - &*PROCESS_HEADERS_SOFT_WIDTH_MAX_NO_GROUP_TREE - } else { - &*PROCESS_HEADERS_SOFT_WIDTH_MAX_NO_GROUP_ELSE - }; - - proc_widget_state.table_width_state.calculated_column_widths = - get_column_widths( - draw_loc.width, - hard_widths, - &soft_widths_min, - soft_widths_max, - &(proc_widget_state - .table_width_state - .desired_column_widths - .iter() - .map(|width| Some(*width)) - .collect::>()), - true, - ); - - // debug!( - // "DCW: {:?}", - // proc_widget_state.table_width_state.desired_column_widths - // ); - // debug!( - // "CCW: {:?}", - // proc_widget_state.table_width_state.calculated_column_widths - // ); - } - - let dcw = &proc_widget_state.table_width_state.desired_column_widths; - let ccw = &proc_widget_state.table_width_state.calculated_column_widths; - - let process_rows = sliced_vec.iter().map(|(data, disabled)| { - let truncated_data = data.iter().zip(hard_widths).enumerate().map( - |(itx, ((entry, alternative), width))| { - if let (Some(desired_col_width), Some(calculated_col_width)) = - (dcw.get(itx), ccw.get(itx)) - { - if width.is_none() { - if *desired_col_width > *calculated_col_width - && *calculated_col_width > 0 - { - let graphemes = - UnicodeSegmentation::graphemes(entry.as_str(), true) - .collect::>(); - - if let Some(alternative) = alternative { - Text::raw(alternative) - } else if graphemes.len() > *calculated_col_width as usize - && *calculated_col_width > 1 - { - // Truncate with ellipsis - let first_n = graphemes - [..(*calculated_col_width as usize - 1)] - .concat(); - Text::raw(format!("{}…", first_n)) - } else { - Text::raw(entry) - } - } else { - Text::raw(entry) - } - } else { - Text::raw(entry) - } - } else { - Text::raw(entry) - } - }, - ); - - if *disabled { - Row::new(truncated_data).style(self.colours.disabled_text_style) - } else { - Row::new(truncated_data) - } - }); - - f.render_stateful_widget( - Table::new(process_rows) - .header( - Row::new(process_headers) - .style(self.colours.table_header_style) - .bottom_margin(table_gap), - ) - .block(process_block) - .highlight_style(highlight_style) - .style(self.colours.text_style) - .widths( - &(proc_widget_state - .table_width_state - .calculated_column_widths - .iter() - .map(|calculated_width| { - Constraint::Length(*calculated_width as u16) - }) - .collect::>()), - ), - margined_draw_loc, - proc_table_state, - ); - } else { - f.render_widget(process_block, margined_draw_loc); - } - - // Check if we need to update columnar bounds... - if recalculate_column_widths - || proc_widget_state.columns.column_header_x_locs.is_none() - || proc_widget_state.columns.column_header_y_loc.is_none() - { - // y location is just the y location of the widget + border size (1 normally, 0 in basic) - proc_widget_state.columns.column_header_y_loc = - Some(draw_loc.y + if draw_border { 1 } else { 0 }); - - // x location is determined using the x locations of the widget; just offset from the left bound - // as appropriate, and use the right bound as limiter. - - let mut current_x_left = draw_loc.x + 1; - let max_x_right = draw_loc.x + draw_loc.width - 1; - - let mut x_locs = vec![]; - - for width in proc_widget_state - .table_width_state - .calculated_column_widths - .iter() - { - let right_bound = current_x_left + width; - - if right_bound < max_x_right { - x_locs.push((current_x_left, right_bound)); - current_x_left = right_bound + 1; - } else { - x_locs.push((current_x_left, max_x_right)); - break; - } - } - - proc_widget_state.columns.column_header_x_locs = Some(x_locs); - } - - if app_state.should_get_widget_bounds() { - // Update draw loc in widget map - if let Some(widget) = app_state.widget_map.get_mut(&widget_id) { - widget.top_left_corner = Some((margined_draw_loc.x, margined_draw_loc.y)); - widget.bottom_right_corner = Some(( - margined_draw_loc.x + margined_draw_loc.width, - margined_draw_loc.y + margined_draw_loc.height, - )); - } - } - } - } - - fn draw_search_field( - &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool, - widget_id: u64, - ) { - fn build_query<'a>( - is_on_widget: bool, grapheme_indices: GraphemeIndices<'a>, start_position: usize, - cursor_position: usize, query: &str, currently_selected_text_style: tui::style::Style, - text_style: tui::style::Style, - ) -> Vec> { - let mut current_grapheme_posn = 0; - - if is_on_widget { - let mut res = grapheme_indices - .filter_map(|grapheme| { - current_grapheme_posn += UnicodeWidthStr::width(grapheme.1); - - if current_grapheme_posn <= start_position { - None - } else { - let styled = if grapheme.0 == cursor_position { - Span::styled(grapheme.1, currently_selected_text_style) - } else { - Span::styled(grapheme.1, text_style) - }; - Some(styled) - } - }) - .collect::>(); - - if cursor_position == query.len() { - res.push(Span::styled(" ", currently_selected_text_style)) - } - - res - } else { - // This is easier - we just need to get a range of graphemes, rather than - // dealing with possibly inserting a cursor (as none is shown!) - - vec![Span::styled(query.to_string(), text_style)] - } - } - - // TODO: Make the cursor scroll back if there's space! - if let Some(proc_widget_state) = - app_state.proc_state.widget_states.get_mut(&(widget_id - 1)) - { - let is_on_widget = widget_id == app_state.current_widget.widget_id; - let num_columns = usize::from(draw_loc.width); - let search_title = "> "; - - let num_chars_for_text = search_title.len(); - let cursor_position = proc_widget_state.get_search_cursor_position(); - let current_cursor_position = proc_widget_state.get_char_cursor_position(); - - let start_position: usize = get_search_start_position( - num_columns - num_chars_for_text - 5, - &proc_widget_state - .process_search_state - .search_state - .cursor_direction, - &mut proc_widget_state - .process_search_state - .search_state - .cursor_bar, - current_cursor_position, - app_state.is_force_redraw, - ); - - let query = proc_widget_state.get_current_search_query().as_str(); - let grapheme_indices = UnicodeSegmentation::grapheme_indices(query, true); - - // TODO: [CURSOR] blank cursor if not selected - // TODO: [CURSOR] blinking cursor? - let query_with_cursor = build_query( - is_on_widget, - grapheme_indices, - start_position, - cursor_position, - query, - self.colours.currently_selected_text_style, - self.colours.text_style, - ); - - let mut search_text = vec![Spans::from({ - let mut search_vec = vec![Span::styled( - search_title, - if is_on_widget { - self.colours.table_header_style - } else { - self.colours.text_style - }, - )]; - search_vec.extend(query_with_cursor); - - search_vec - })]; - - // Text options shamelessly stolen from VS Code. - let case_style = if !proc_widget_state.process_search_state.is_ignoring_case { - self.colours.currently_selected_text_style - } else { - self.colours.text_style - }; - - let whole_word_style = if proc_widget_state - .process_search_state - .is_searching_whole_word - { - self.colours.currently_selected_text_style - } else { - self.colours.text_style - }; - - let regex_style = if proc_widget_state - .process_search_state - .is_searching_with_regex - { - self.colours.currently_selected_text_style - } else { - self.colours.text_style - }; - - // FIXME: [MOUSE] Mouse support for these in search - // FIXME: [MOVEMENT] Movement support for these in search - let option_text = Spans::from(vec![ - Span::styled( - format!("Case({})", if self.is_mac_os { "F1" } else { "Alt+C" }), - case_style, - ), - Span::raw(" "), - Span::styled( - format!("Whole({})", if self.is_mac_os { "F2" } else { "Alt+W" }), - whole_word_style, - ), - Span::raw(" "), - Span::styled( - format!("Regex({})", if self.is_mac_os { "F3" } else { "Alt+R" }), - regex_style, - ), - ]); - - search_text.push(Spans::from(Span::styled( - if let Some(err) = &proc_widget_state - .process_search_state - .search_state - .error_message - { - err.as_str() - } else { - "" - }, - self.colours.invalid_query_style, - ))); - search_text.push(option_text); - - let current_border_style = if proc_widget_state - .process_search_state - .search_state - .is_invalid_search - { - self.colours.invalid_query_style - } else if is_on_widget { - self.colours.highlighted_border_style - } else { - self.colours.border_style - }; - - let title = Span::styled( - if draw_border { - const TITLE_BASE: &str = " Esc to close "; - let repeat_num = - usize::from(draw_loc.width).saturating_sub(TITLE_BASE.chars().count() + 2); - format!("{} Esc to close ", "─".repeat(repeat_num)) - } else { - String::new() - }, - current_border_style, - ); - - let process_search_block = if draw_border { - Block::default() - .title(title) - .borders(Borders::ALL) - .border_style(current_border_style) - } else if is_on_widget { - Block::default() - .borders(*SIDE_BORDERS) - .border_style(current_border_style) - } else { - Block::default().borders(Borders::NONE) - }; - - let margined_draw_loc = Layout::default() - .constraints([Constraint::Percentage(100)]) - .horizontal_margin(if is_on_widget || draw_border { 0 } else { 1 }) - .direction(Direction::Horizontal) - .split(draw_loc)[0]; - - f.render_widget( - Paragraph::new(search_text) - .block(process_search_block) - .style(self.colours.text_style) - .alignment(Alignment::Left), - margined_draw_loc, - ); - - if app_state.should_get_widget_bounds() { - // Update draw loc in widget map - if let Some(widget) = app_state.widget_map.get_mut(&widget_id) { - widget.top_left_corner = Some((margined_draw_loc.x, margined_draw_loc.y)); - widget.bottom_right_corner = Some(( - margined_draw_loc.x + margined_draw_loc.width, - margined_draw_loc.y + margined_draw_loc.height, - )); - } - } - } - } - - fn draw_process_sort( - &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool, - widget_id: u64, - ) { - let is_on_widget = widget_id == app_state.current_widget.widget_id; - - if let Some(proc_widget_state) = - app_state.proc_state.widget_states.get_mut(&(widget_id - 2)) - { - let current_scroll_position = proc_widget_state.columns.current_scroll_position; - let sort_string = proc_widget_state - .columns - .ordered_columns - .iter() - .filter(|column_type| { - proc_widget_state - .columns - .column_mapping - .get(column_type) - .unwrap() - .enabled - }) - .map(|column_type| column_type.to_string()) - .collect::>(); - - let table_gap = if draw_loc.height < TABLE_GAP_HEIGHT_LIMIT { - 0 - } else { - app_state.app_config_fields.table_gap - }; - let position = get_start_position( - usize::from( - (draw_loc.height + (1 - table_gap)).saturating_sub(self.table_height_offset), - ), - &proc_widget_state.columns.scroll_direction, - &mut proc_widget_state.columns.previous_scroll_position, - current_scroll_position, - app_state.is_force_redraw, - ); - - // Sanity check - let start_position = if position >= sort_string.len() { - sort_string.len().saturating_sub(1) - } else { - position - }; - - let sliced_vec = &sort_string[start_position..]; - - let sort_options = sliced_vec - .iter() - .map(|column| Row::new(vec![column.as_str()])); - - let column_state = &mut proc_widget_state.columns.column_state; - column_state.select(Some( - proc_widget_state - .columns - .current_scroll_position - .saturating_sub(start_position), - )); - let current_border_style = if proc_widget_state - .process_search_state - .search_state - .is_invalid_search - { - self.colours.invalid_query_style - } else if is_on_widget { - self.colours.highlighted_border_style - } else { - self.colours.border_style - }; - - let process_sort_block = if draw_border { - Block::default() - .borders(Borders::ALL) - .border_style(current_border_style) - } else if is_on_widget { - Block::default() - .borders(*SIDE_BORDERS) - .border_style(current_border_style) - } else { - Block::default().borders(Borders::NONE) - }; - - let highlight_style = if is_on_widget { - self.colours.currently_selected_text_style - } else { - self.colours.text_style - }; - - let margined_draw_loc = Layout::default() - .constraints([Constraint::Percentage(100)]) - .horizontal_margin(if is_on_widget || draw_border { 0 } else { 1 }) - .direction(Direction::Horizontal) - .split(draw_loc)[0]; - - f.render_stateful_widget( - Table::new(sort_options) - .header( - Row::new(vec!["Sort By"]) - .style(self.colours.table_header_style) - .bottom_margin(table_gap), - ) - .block(process_sort_block) - .highlight_style(highlight_style) - .style(self.colours.text_style) - .widths(&[Constraint::Percentage(100)]), - margined_draw_loc, - column_state, - ); - - if app_state.should_get_widget_bounds() { - // Update draw loc in widget map - if let Some(widget) = app_state.widget_map.get_mut(&widget_id) { - widget.top_left_corner = Some((margined_draw_loc.x, margined_draw_loc.y)); - widget.bottom_right_corner = Some(( - margined_draw_loc.x + margined_draw_loc.width, - margined_draw_loc.y + margined_draw_loc.height, - )); - } - } - } - } -} diff --git a/src/clap.rs b/src/clap.rs index 8604c5a8..e31b6182 100644 --- a/src/clap.rs +++ b/src/clap.rs @@ -84,7 +84,7 @@ pub fn get_matches() -> clap::ArgMatches<'static> { build_app().get_matches() } -// TODO: Refactor this a bit, it's quite messy atm +// TODO: [Refactor] Refactor the clap app creation a bit, it's quite messy atm pub fn build_app() -> App<'static, 'static> { // Temps let kelvin = Arg::with_name("kelvin") @@ -149,7 +149,7 @@ When searching for a process, enables case sensitivity by default.\n\n", Sets process CPU% usage to be based on the current system CPU% usage rather than total CPU usage.\n\n", ); - // TODO: [DEBUG] Add a proper debugging solution. + // TODO: [Feature] Add a proper debugging solution. Potentially, add a "diagnose" option to just see if we can gather data. // let debug = Arg::with_name("debug") // .long("debug") // .help("Enables debug logging.") @@ -157,7 +157,6 @@ rather than total CPU usage.\n\n", // "\ // Enables debug logging. The program will print where it logged to after running.", // ); - // TODO: [DIAGNOSE] Add a diagnose option to help with debugging. let disable_click = Arg::with_name("disable_click") .long("disable_click") .help("Disables mouse clicks.") @@ -176,7 +175,7 @@ Uses a dot marker for graphs as opposed to the default braille marker.\n\n", ); - let group = Arg::with_name("group") // FIXME: Rename this to something like "group_process", would be "breaking" though. + let group = Arg::with_name("group") // TODO: [Config, Refactor, Breaking] Rename this to something like "group_process", would be "breaking" though. .short("g") .long("group") .help("Groups processes with the same name by default.") diff --git a/src/constants.rs b/src/constants.rs index 57e17861..103ed626 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -508,7 +508,7 @@ pub const DEFAULT_BASIC_BATTERY_LAYOUT: &str = r##" // Config and flags pub const DEFAULT_CONFIG_FILE_PATH: &str = "bottom/bottom.toml"; -// TODO: Eventually deprecate this. +// TODO: [Config, Deprecation/Refactor] Eventually deprecate this with better configs. pub const CONFIG_TEXT: &str = r##"# This is a default config file for bottom. All of the settings are commented # out by default; if you wish to change them uncomment and modify as you see # fit. diff --git a/src/data_conversion.rs b/src/data_conversion.rs index c62ae163..e32982b2 100644 --- a/src/data_conversion.rs +++ b/src/data_conversion.rs @@ -3,11 +3,11 @@ use crate::app::data_harvester::temperature::TemperatureType; use crate::app::text_table::TextTableData; use crate::app::DataCollection; -use crate::{app::AxisScaling, units::data_units::DataUnit, Pid}; use crate::{ - app::{data_harvester, ProcWidgetState}, + app::data_harvester, utils::{self, gen_util::*}, }; +use crate::{app::AxisScaling, units::data_units::DataUnit, Pid}; use data_harvester::processes::ProcessSorting; use fxhash::FxBuildHasher; use indexmap::IndexSet; @@ -47,7 +47,7 @@ pub struct ConvertedNetworkData { pub tx_display: String, pub total_rx_display: Option, pub total_tx_display: Option, - // TODO: [NETWORKING] add min/max/mean of each + // TODO: [Feature] Networking - add the following stats in the future! // min_rx : f64, // max_rx : f64, // mean_rx: f64, @@ -56,7 +56,7 @@ pub struct ConvertedNetworkData { // mean_tx: f64, } -// TODO: [REFACTOR] Process data... stuff really needs a rewrite. Again. +// TODO: [Refactor] Process data might need some refactoring lol #[derive(Clone, Default, Debug)] pub struct ConvertedProcessData { pub pid: Pid, @@ -321,7 +321,7 @@ pub fn convert_mem_labels( } } - // TODO: Should probably make this only return none if no data is left/visible? + // TODO: [Refactor] Should probably make this only return none if no data is left/visible? ( if current_data.memory_harvest.mem_total_in_kib > 0 { Some(( @@ -586,7 +586,7 @@ pub fn convert_process_data( existing_converted_process_data: &mut HashMap, #[cfg(target_family = "unix")] user_table: &mut data_harvester::processes::UserTable, ) { - // TODO [THREAD]: Thread highlighting and hiding support + // TODO: [Feature] Thread highlighting and hiding support; can we also count number of threads per process and display it as a column? // For macOS see https://github.com/hishamhm/htop/pull/848/files let mut complete_pid_set: fxhash::FxHashSet = @@ -715,7 +715,7 @@ fn tree_process_data( filtered_process_data: &[ConvertedProcessData], is_using_command: bool, sorting_type: &ProcessSorting, is_sort_descending: bool, ) -> Vec { - // TODO: [TREE] Option to sort usage by total branch usage or individual value usage? + // TODO: [Feature] Option to sort usage by total branch usage or individual value usage? // Let's first build up a (really terrible) parent -> child mapping... // At the same time, let's make a mapping of PID -> process data! @@ -1178,78 +1178,79 @@ fn tree_process_data( .collect::>() } -// FIXME: [OPT] This is an easy target for optimization, too many to_strings! -fn stringify_process_data( - proc_widget_state: &ProcWidgetState, finalized_process_data: &[ConvertedProcessData], -) -> Vec<(Vec<(String, Option)>, bool)> { - let is_proc_widget_grouped = proc_widget_state.is_grouped; - let is_using_command = proc_widget_state.is_using_command; - let is_tree = proc_widget_state.is_tree_mode; - let mem_enabled = proc_widget_state.columns.is_enabled(&ProcessSorting::Mem); +// FIXME: [URGENT] Delete this +// // TODO: [Optimization] This is an easy target for optimization, too many to_strings! +// fn stringify_process_data( +// proc_widget_state: &ProcWidgetState, finalized_process_data: &[ConvertedProcessData], +// ) -> Vec<(Vec<(String, Option)>, bool)> { +// let is_proc_widget_grouped = proc_widget_state.is_grouped; +// let is_using_command = proc_widget_state.is_using_command; +// let is_tree = proc_widget_state.is_tree_mode; +// let mem_enabled = proc_widget_state.columns.is_enabled(&ProcessSorting::Mem); - finalized_process_data - .iter() - .map(|process| { - ( - vec![ - ( - if is_proc_widget_grouped { - process.group_pids.len().to_string() - } else { - process.pid.to_string() - }, - None, - ), - ( - if is_tree { - if let Some(prefix) = &process.process_description_prefix { - prefix.clone() - } else { - String::default() - } - } else if is_using_command { - process.command.clone() - } else { - process.name.clone() - }, - None, - ), - (format!("{:.1}%", process.cpu_percent_usage), None), - ( - if mem_enabled { - if process.mem_usage_bytes <= GIBI_LIMIT { - format!("{:.0}{}", process.mem_usage_str.0, process.mem_usage_str.1) - } else { - format!("{:.1}{}", process.mem_usage_str.0, process.mem_usage_str.1) - } - } else { - format!("{:.1}%", process.mem_percent_usage) - }, - None, - ), - (process.read_per_sec.clone(), None), - (process.write_per_sec.clone(), None), - (process.total_read.clone(), None), - (process.total_write.clone(), None), - #[cfg(target_family = "unix")] - ( - if let Some(user) = &process.user { - user.clone() - } else { - "N/A".to_string() - }, - None, - ), - ( - process.process_state.clone(), - Some(process.process_char.to_string()), - ), - ], - process.is_disabled_entry, - ) - }) - .collect() -} +// finalized_process_data +// .iter() +// .map(|process| { +// ( +// vec![ +// ( +// if is_proc_widget_grouped { +// process.group_pids.len().to_string() +// } else { +// process.pid.to_string() +// }, +// None, +// ), +// ( +// if is_tree { +// if let Some(prefix) = &process.process_description_prefix { +// prefix.clone() +// } else { +// String::default() +// } +// } else if is_using_command { +// process.command.clone() +// } else { +// process.name.clone() +// }, +// None, +// ), +// (format!("{:.1}%", process.cpu_percent_usage), None), +// ( +// if mem_enabled { +// if process.mem_usage_bytes <= GIBI_LIMIT { +// format!("{:.0}{}", process.mem_usage_str.0, process.mem_usage_str.1) +// } else { +// format!("{:.1}{}", process.mem_usage_str.0, process.mem_usage_str.1) +// } +// } else { +// format!("{:.1}%", process.mem_percent_usage) +// }, +// None, +// ), +// (process.read_per_sec.clone(), None), +// (process.write_per_sec.clone(), None), +// (process.total_read.clone(), None), +// (process.total_write.clone(), None), +// #[cfg(target_family = "unix")] +// ( +// if let Some(user) = &process.user { +// user.clone() +// } else { +// "N/A".to_string() +// }, +// None, +// ), +// ( +// process.process_state.clone(), +// Some(process.process_char.to_string()), +// ), +// ], +// process.is_disabled_entry, +// ) +// }) +// .collect() +// } /// Takes a set of converted process data and groups it together. /// @@ -1364,7 +1365,7 @@ pub fn convert_battery_harvest(current_data: &DataCollection) -> Vec, termination_ctrl_lock: Arc>, ) -> std::thread::JoinHandle<()> { thread::spawn(move || { - // TODO: Maybe experiment with removing these timers. Look into using buffers instead? + // TODO: [Optimization, Input] Maybe experiment with removing these timers. Look into using buffers instead? let mut mouse_timer = Instant::now(); let mut keyboard_timer = Instant::now(); diff --git a/src/options.rs b/src/options.rs index 750afbd8..bd257ff4 100644 --- a/src/options.rs +++ b/src/options.rs @@ -150,7 +150,7 @@ fn default_as_true() -> bool { #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct IgnoreList { #[serde(default = "default_as_true")] - // TODO: Deprecate and/or rename, current name sounds awful. + // TODO: [Config] Deprecate and/or rename, current name sounds awful. // Maybe to something like "deny_entries"? Currently it defaults to a denylist anyways, so maybe "allow_entries"? pub is_list_ignored: bool, pub list: Vec, diff --git a/src/utils/error.rs b/src/utils/error.rs index 7a9009d2..cd41a44e 100644 --- a/src/utils/error.rs +++ b/src/utils/error.rs @@ -52,7 +52,6 @@ impl From for BottomError { } } - impl From for BottomError { fn from(err: std::num::ParseIntError) -> Self { BottomError::ConfigError(err.to_string()) diff --git a/tests/layout_management_tests.rs b/tests/layout_management_tests.rs index c5c96062..d2150cd6 100644 --- a/tests/layout_management_tests.rs +++ b/tests/layout_management_tests.rs @@ -1,3 +1,3 @@ -//! Mocks layout management, so we can check if we broke anything. +//! Mocks layout management. -// TODO: Redo testing. +// FIXME: [URGENT] Redo testing for layout management. diff --git a/tests/layout_movement_tests.rs b/tests/layout_movement_tests.rs index 4c59e26d..55167dd6 100644 --- a/tests/layout_movement_tests.rs +++ b/tests/layout_movement_tests.rs @@ -1 +1,3 @@ -// TODO: Redo testing +//! Mocks layout movement. + +// FIXME: [URGENT] Add testing for layout movement.