diff --git a/.travis.yml b/.travis.yml index 6bbb8e20..1256416b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,9 @@ jobs: - rust: nightly - env: TARGET=x86_64-pc-windows-gnu # Seems to cause problems. TODO: Add test for it, but keep allow fail. fast_finish: true +branches: + only: + - master before_install: - export RUST_BACKTRACE=1 diff --git a/Cargo.toml b/Cargo.toml index 64bc1db7..a2fb8289 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,8 @@ lto = "fat" codegen-units = 1 [dependencies] -chrono = "0.4.10" +crossterm = "0.16" +chrono = "0.4.11" clap = "2.33.0" dirs = "2.0.2" fern = "0.6.0" @@ -32,11 +33,11 @@ heim = "0.0.10" log = "0.4.8" regex = "1.3.4" sysinfo = "0.11" -crossterm = "0.16" +toml = "0.5.6" tui = {version = "0.8", features = ["crossterm"], default-features = false } +typed-builder = "0.5.1" lazy_static = "1.4.0" backtrace = "0.3" -toml = "0.5.6" serde = {version = "1.0", features = ["derive"] } unicode-segmentation = "1.6.0" unicode-width = "0.1.7" diff --git a/README.md b/README.md index 25d4b5c4..58b4e922 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ For information about config files, see [this document](./docs/config.md) for mo ## Installation -In all cases you can install the in-development version by cloning from this repo and using `cargo build --release`. This is built and tested with Rust Stable (1.41 as of writing). +In all cases you can install the in-development version by cloning from this repo and using `cargo build --release`. This is built and tested with Rust Stable (1.42 as of writing). In addition to the below methods, you can manually build from the [Releases](https://github.com/ClementTsang/bottom/releases) page by downloading and building. @@ -257,13 +257,13 @@ Thanks to those who have contributed: - [dirs](https://github.com/soc/dirs-rs) - [fern](https://github.com/daboross/fern) - [futures-rs](https://github.com/rust-lang-nursery/futures-rs) - - [futures-timer](https://github.com/rustasync/futures-timer) - [heim](https://github.com/heim-rs/heim) - [lazy_static](https://github.com/rust-lang-nursery/lazy-static.rs) - [log](https://github.com/rust-lang-nursery/log) + - [serde](https://github.com/serde-rs/serde) - [sysinfo](https://github.com/GuillaumeGomez/sysinfo) - - [tokio](https://github.com/tokio-rs/tokio) - [toml-rs](https://github.com/alexcrichton/toml-rs) + - [typed-builder](https://github.com/idanarye/rust-typed-builder) - [tui-rs](https://github.com/fdehau/tui-rs) - [unicode-segmentation](https://github.com/unicode-rs/unicode-segmentation) - [unicode-width](https://github.com/unicode-rs/unicode-width) diff --git a/src/app.rs b/src/app.rs index d5694e6d..4cca1d81 100644 --- a/src/app.rs +++ b/src/app.rs @@ -4,6 +4,8 @@ use std::time::Instant; use unicode_segmentation::GraphemeCursor; use unicode_width::{UnicodeWidthChar, UnicodeWidthStr}; +use typed_builder::*; + use data_farmer::*; use data_harvester::{processes, temperature}; @@ -108,8 +110,8 @@ impl Default for AppScrollState { /// AppSearchState deals with generic searching (I might do this in the future). pub struct AppSearchState { pub is_enabled: bool, - current_search_query: String, - current_regex: Option>, + pub current_search_query: String, + pub current_regex: Option>, pub is_blank_search: bool, pub is_invalid_search: bool, pub grapheme_cursor: GraphemeCursor, @@ -137,10 +139,11 @@ impl Default for AppSearchState { impl AppSearchState { /// Returns a reset but still enabled app search state - pub fn reset() -> Self { - let mut app_search_state = AppSearchState::default(); - app_search_state.is_enabled = true; - 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 { @@ -211,7 +214,6 @@ impl Default for AppHelpDialogState { /// AppConfigFields is meant to cover basic fields that would normally be set /// by config files or launch options. -#[derive(Default)] pub struct AppConfigFields { pub update_rate_in_milliseconds: u64, pub temperature_type: temperature::TemperatureType, @@ -233,21 +235,21 @@ pub struct NetState { pub is_showing_rx: bool, pub is_showing_tx: bool, pub zoom_level: f64, - pub display_time: u64, + pub current_display_time: u64, pub force_update: bool, - pub display_time_instant: Option, + pub autohide_timer: Option, } -impl Default for NetState { - fn default() -> Self { +impl NetState { + pub fn init(current_display_time: u64, autohide_timer: Option) -> Self { NetState { is_showing_tray: false, is_showing_rx: true, is_showing_tx: true, zoom_level: 100.0, - display_time: constants::DEFAULT_TIME_MILLISECONDS, + current_display_time, force_update: false, - display_time_instant: None, + autohide_timer, } } } @@ -258,21 +260,21 @@ pub struct CpuState { pub zoom_level: f64, pub core_show_vec: Vec, pub num_cpus_shown: u64, - pub display_time: u64, + pub current_display_time: u64, pub force_update: bool, - pub display_time_instant: Option, + pub autohide_timer: Option, } -impl Default for CpuState { - fn default() -> Self { +impl CpuState { + pub fn init(current_display_time: u64, autohide_timer: Option) -> Self { CpuState { is_showing_tray: false, zoom_level: 100.0, core_show_vec: Vec::new(), num_cpus_shown: 0, - display_time: constants::DEFAULT_TIME_MILLISECONDS, + current_display_time, force_update: false, - display_time_instant: None, + autohide_timer, } } } @@ -283,136 +285,117 @@ pub struct MemState { pub is_showing_ram: bool, pub is_showing_swap: bool, pub zoom_level: f64, - pub display_time: u64, + pub current_display_time: u64, pub force_update: bool, - pub display_time_instant: Option, + pub autohide_timer: Option, } -impl Default for MemState { - fn default() -> Self { +impl MemState { + pub fn init(current_display_time: u64, autohide_timer: Option) -> Self { MemState { is_showing_tray: false, is_showing_ram: true, is_showing_swap: true, zoom_level: 100.0, - display_time: constants::DEFAULT_TIME_MILLISECONDS, + current_display_time, force_update: false, - display_time_instant: None, + autohide_timer, } } } +#[derive(TypedBuilder)] pub struct App { + #[builder(default=processes::ProcessSorting::CPU, setter(skip))] pub process_sorting_type: processes::ProcessSorting, + + #[builder(default = true, setter(skip))] pub process_sorting_reverse: bool, + + #[builder(default = false, setter(skip))] pub force_update_processes: bool, + + #[builder(default, setter(skip))] pub app_scroll_positions: AppScrollState, - pub current_widget_selected: WidgetPosition, - pub previous_basic_table_selected: WidgetPosition, + + #[builder(default = false, setter(skip))] awaiting_second_char: bool, + + #[builder(default, setter(skip))] second_char: Option, + + #[builder(default, setter(skip))] pub dd_err: Option, + + #[builder(default, setter(skip))] to_delete_process_list: Option<(String, Vec)>, + + #[builder(default = false, setter(skip))] pub is_frozen: bool, + + #[builder(default = Instant::now(), setter(skip))] last_key_press: Instant, + + #[builder(default, setter(skip))] pub canvas_data: canvas::DisplayableData, + + #[builder(default = false)] enable_grouping: bool, + + #[builder(default, setter(skip))] pub data_collection: DataCollection, + + #[builder(default, setter(skip))] pub process_search_state: ProcessSearchState, + + #[builder(default, setter(skip))] pub delete_dialog_state: AppDeleteDialogState, + + #[builder(default, setter(skip))] pub help_dialog_state: AppHelpDialogState, - pub app_config_fields: AppConfigFields, + + #[builder(default = false, setter(skip))] pub is_expanded: bool, + + #[builder(default = false, setter(skip))] pub is_resized: bool, + pub cpu_state: CpuState, pub mem_state: MemState, pub net_state: NetState, + + pub app_config_fields: AppConfigFields, + pub current_widget_selected: WidgetPosition, + pub previous_basic_table_selected: WidgetPosition, } impl App { - #[allow(clippy::too_many_arguments)] - // TODO: [REFACTOR] use builder pattern instead. - pub fn new( - show_average_cpu: bool, temperature_type: temperature::TemperatureType, - update_rate_in_milliseconds: u64, use_dot: bool, left_legend: bool, - use_current_cpu_total: bool, current_widget_selected: WidgetPosition, - show_disabled_data: bool, use_basic_mode: bool, default_time_value: u64, - time_interval: u64, - ) -> App { - let mut cpu_state = CpuState::default(); - let mut mem_state = MemState::default(); - let mut net_state = NetState::default(); - - cpu_state.display_time = default_time_value; - mem_state.display_time = default_time_value; - net_state.display_time = default_time_value; - - App { - process_sorting_type: processes::ProcessSorting::CPU, - process_sorting_reverse: true, - force_update_processes: false, - current_widget_selected: if use_basic_mode { - match current_widget_selected { - WidgetPosition::Cpu => WidgetPosition::BasicCpu, - WidgetPosition::Network => WidgetPosition::BasicNet, - WidgetPosition::Mem => WidgetPosition::BasicMem, - _ => current_widget_selected, - } - } else { - current_widget_selected - }, - previous_basic_table_selected: if current_widget_selected.is_widget_table() { - current_widget_selected - } else { - WidgetPosition::Process - }, - app_scroll_positions: AppScrollState::default(), - awaiting_second_char: false, - second_char: None, - dd_err: None, - to_delete_process_list: None, - is_frozen: false, - last_key_press: Instant::now(), - canvas_data: canvas::DisplayableData::default(), - enable_grouping: false, - data_collection: DataCollection::default(), - process_search_state: ProcessSearchState::default(), - delete_dialog_state: AppDeleteDialogState::default(), - help_dialog_state: AppHelpDialogState::default(), - app_config_fields: AppConfigFields { - show_average_cpu, - temperature_type, - use_dot, - update_rate_in_milliseconds, - left_legend, - use_current_cpu_total, - show_disabled_data, - use_basic_mode, - default_time_value, - time_interval, - hide_time: false, - autohide_time: false, - }, - is_expanded: false, - is_resized: false, - cpu_state, - mem_state, - net_state, - } - } - pub fn reset(&mut self) { + // Reset multi self.reset_multi_tap_keys(); + + // Reset dialog state self.help_dialog_state.is_showing_help = false; self.delete_dialog_state.is_showing_dd = false; - if self.process_search_state.search_state.is_enabled { - self.current_widget_selected = WidgetPosition::Process; - self.process_search_state.search_state.is_enabled = false; - } - self.process_search_state.search_state.current_search_query = String::new(); - self.process_search_state.is_searching_with_pid = false; + + // Close search and reset it + self.process_search_state.search_state.reset(); + self.force_update_processes = true; + + // Clear current delete list self.to_delete_process_list = None; self.dd_err = None; + + // Unfreeze. + self.is_frozen = false; + + // Reset zoom + self.reset_cpu_zoom(); + self.reset_mem_zoom(); + self.reset_net_zoom(); + + // Reset data + self.data_collection.reset(); } pub fn on_esc(&mut self) { @@ -783,7 +766,7 @@ impl App { pub fn clear_search(&mut self) { if let WidgetPosition::ProcessSearch = self.current_widget_selected { self.force_update_processes = true; - self.process_search_state.search_state = AppSearchState::reset(); + self.process_search_state.search_state.reset(); } } @@ -795,7 +778,7 @@ impl App { &self.process_search_state.search_state.current_search_query[start_position..], start_position, ) - .unwrap(); // TODO: [UNWRAP] unwrap in this and walk_back seem sketch + .unwrap(); } pub fn search_walk_back(&mut self, start_position: usize) { @@ -1544,50 +1527,53 @@ impl App { fn zoom_out(&mut self) { match self.current_widget_selected { WidgetPosition::Cpu => { - let new_time = self.cpu_state.display_time + self.app_config_fields.time_interval; + let new_time = + self.cpu_state.current_display_time + self.app_config_fields.time_interval; if new_time <= constants::STALE_MAX_MILLISECONDS { - self.cpu_state.display_time = new_time; + self.cpu_state.current_display_time = new_time; self.cpu_state.force_update = true; if self.app_config_fields.autohide_time { - self.cpu_state.display_time_instant = Some(Instant::now()); + self.cpu_state.autohide_timer = Some(Instant::now()); } - } else if self.cpu_state.display_time != constants::STALE_MAX_MILLISECONDS { - self.cpu_state.display_time = constants::STALE_MAX_MILLISECONDS; + } else if self.cpu_state.current_display_time != constants::STALE_MAX_MILLISECONDS { + self.cpu_state.current_display_time = constants::STALE_MAX_MILLISECONDS; self.cpu_state.force_update = true; if self.app_config_fields.autohide_time { - self.cpu_state.display_time_instant = Some(Instant::now()); + self.cpu_state.autohide_timer = Some(Instant::now()); } } } WidgetPosition::Mem => { - let new_time = self.mem_state.display_time + self.app_config_fields.time_interval; + let new_time = + self.mem_state.current_display_time + self.app_config_fields.time_interval; if new_time <= constants::STALE_MAX_MILLISECONDS { - self.mem_state.display_time = new_time; + self.mem_state.current_display_time = new_time; self.mem_state.force_update = true; if self.app_config_fields.autohide_time { - self.mem_state.display_time_instant = Some(Instant::now()); + self.mem_state.autohide_timer = Some(Instant::now()); } - } else if self.mem_state.display_time != constants::STALE_MAX_MILLISECONDS { - self.mem_state.display_time = constants::STALE_MAX_MILLISECONDS; + } else if self.mem_state.current_display_time != constants::STALE_MAX_MILLISECONDS { + self.mem_state.current_display_time = constants::STALE_MAX_MILLISECONDS; self.mem_state.force_update = true; if self.app_config_fields.autohide_time { - self.mem_state.display_time_instant = Some(Instant::now()); + self.mem_state.autohide_timer = Some(Instant::now()); } } } WidgetPosition::Network => { - let new_time = self.net_state.display_time + self.app_config_fields.time_interval; + let new_time = + self.net_state.current_display_time + self.app_config_fields.time_interval; if new_time <= constants::STALE_MAX_MILLISECONDS { - self.net_state.display_time = new_time; + self.net_state.current_display_time = new_time; self.net_state.force_update = true; if self.app_config_fields.autohide_time { - self.net_state.display_time_instant = Some(Instant::now()); + self.net_state.autohide_timer = Some(Instant::now()); } - } else if self.net_state.display_time != constants::STALE_MAX_MILLISECONDS { - self.net_state.display_time = constants::STALE_MAX_MILLISECONDS; + } else if self.net_state.current_display_time != constants::STALE_MAX_MILLISECONDS { + self.net_state.current_display_time = constants::STALE_MAX_MILLISECONDS; self.net_state.force_update = true; if self.app_config_fields.autohide_time { - self.net_state.display_time_instant = Some(Instant::now()); + self.net_state.autohide_timer = Some(Instant::now()); } } } @@ -1598,50 +1584,53 @@ impl App { fn zoom_in(&mut self) { match self.current_widget_selected { WidgetPosition::Cpu => { - let new_time = self.cpu_state.display_time - self.app_config_fields.time_interval; + let new_time = + self.cpu_state.current_display_time - self.app_config_fields.time_interval; if new_time >= constants::STALE_MIN_MILLISECONDS { - self.cpu_state.display_time = new_time; + self.cpu_state.current_display_time = new_time; self.cpu_state.force_update = true; if self.app_config_fields.autohide_time { - self.cpu_state.display_time_instant = Some(Instant::now()); + self.cpu_state.autohide_timer = Some(Instant::now()); } - } else if self.cpu_state.display_time != constants::STALE_MIN_MILLISECONDS { - self.cpu_state.display_time = constants::STALE_MIN_MILLISECONDS; + } else if self.cpu_state.current_display_time != constants::STALE_MIN_MILLISECONDS { + self.cpu_state.current_display_time = constants::STALE_MIN_MILLISECONDS; self.cpu_state.force_update = true; if self.app_config_fields.autohide_time { - self.cpu_state.display_time_instant = Some(Instant::now()); + self.cpu_state.autohide_timer = Some(Instant::now()); } } } WidgetPosition::Mem => { - let new_time = self.mem_state.display_time - self.app_config_fields.time_interval; + let new_time = + self.mem_state.current_display_time - self.app_config_fields.time_interval; if new_time >= constants::STALE_MIN_MILLISECONDS { - self.mem_state.display_time = new_time; + self.mem_state.current_display_time = new_time; self.mem_state.force_update = true; if self.app_config_fields.autohide_time { - self.mem_state.display_time_instant = Some(Instant::now()); + self.mem_state.autohide_timer = Some(Instant::now()); } - } else if self.mem_state.display_time != constants::STALE_MIN_MILLISECONDS { - self.mem_state.display_time = constants::STALE_MIN_MILLISECONDS; + } else if self.mem_state.current_display_time != constants::STALE_MIN_MILLISECONDS { + self.mem_state.current_display_time = constants::STALE_MIN_MILLISECONDS; self.mem_state.force_update = true; if self.app_config_fields.autohide_time { - self.mem_state.display_time_instant = Some(Instant::now()); + self.mem_state.autohide_timer = Some(Instant::now()); } } } WidgetPosition::Network => { - let new_time = self.net_state.display_time - self.app_config_fields.time_interval; + let new_time = + self.net_state.current_display_time - self.app_config_fields.time_interval; if new_time >= constants::STALE_MIN_MILLISECONDS { - self.net_state.display_time = new_time; + self.net_state.current_display_time = new_time; self.net_state.force_update = true; if self.app_config_fields.autohide_time { - self.net_state.display_time_instant = Some(Instant::now()); + self.net_state.autohide_timer = Some(Instant::now()); } - } else if self.net_state.display_time != constants::STALE_MIN_MILLISECONDS { - self.net_state.display_time = constants::STALE_MIN_MILLISECONDS; + } else if self.net_state.current_display_time != constants::STALE_MIN_MILLISECONDS { + self.net_state.current_display_time = constants::STALE_MIN_MILLISECONDS; self.net_state.force_update = true; if self.app_config_fields.autohide_time { - self.net_state.display_time_instant = Some(Instant::now()); + self.net_state.autohide_timer = Some(Instant::now()); } } } @@ -1649,29 +1638,35 @@ impl App { } } + fn reset_cpu_zoom(&mut self) { + self.cpu_state.current_display_time = self.app_config_fields.default_time_value; + self.cpu_state.force_update = true; + if self.app_config_fields.autohide_time { + self.cpu_state.autohide_timer = Some(Instant::now()); + } + } + + fn reset_mem_zoom(&mut self) { + self.mem_state.current_display_time = self.app_config_fields.default_time_value; + self.mem_state.force_update = true; + if self.app_config_fields.autohide_time { + self.mem_state.autohide_timer = Some(Instant::now()); + } + } + + fn reset_net_zoom(&mut self) { + self.net_state.current_display_time = self.app_config_fields.default_time_value; + self.net_state.force_update = true; + if self.app_config_fields.autohide_time { + self.net_state.autohide_timer = Some(Instant::now()); + } + } + fn reset_zoom(&mut self) { match self.current_widget_selected { - WidgetPosition::Cpu => { - self.cpu_state.display_time = self.app_config_fields.default_time_value; - self.cpu_state.force_update = true; - if self.app_config_fields.autohide_time { - self.cpu_state.display_time_instant = Some(Instant::now()); - } - } - WidgetPosition::Mem => { - self.mem_state.display_time = self.app_config_fields.default_time_value; - self.mem_state.force_update = true; - if self.app_config_fields.autohide_time { - self.mem_state.display_time_instant = Some(Instant::now()); - } - } - WidgetPosition::Network => { - self.net_state.display_time = self.app_config_fields.default_time_value; - self.net_state.force_update = true; - if self.app_config_fields.autohide_time { - self.net_state.display_time_instant = Some(Instant::now()); - } - } + WidgetPosition::Cpu => self.reset_cpu_zoom(), + WidgetPosition::Mem => self.reset_mem_zoom(), + WidgetPosition::Network => self.reset_net_zoom(), _ => {} } } diff --git a/src/app/data_farmer.rs b/src/app/data_farmer.rs index 5fa766a5..43fe9cba 100644 --- a/src/app/data_farmer.rs +++ b/src/app/data_farmer.rs @@ -80,6 +80,20 @@ impl Default for DataCollection { } impl DataCollection { + pub fn reset(&mut self) { + self.timed_data_vec = Vec::default(); + self.network_harvest = network::NetworkHarvest::default(); + self.memory_harvest = mem::MemHarvest::default(); + self.swap_harvest = mem::MemHarvest::default(); + self.cpu_harvest = cpu::CPUHarvest::default(); + self.process_harvest = Vec::default(); + self.disk_harvest = Vec::default(); + self.io_harvest = disks::IOHarvest::default(); + self.io_labels = Vec::default(); + self.io_prev = Vec::default(); + self.temp_harvest = Vec::default(); + } + pub fn set_frozen_time(&mut self) { self.frozen_instant = Some(self.current_instant); } diff --git a/src/app/data_harvester.rs b/src/app/data_harvester.rs index 1003aa87..e4b59970 100644 --- a/src/app/data_harvester.rs +++ b/src/app/data_harvester.rs @@ -4,6 +4,8 @@ use std::{collections::HashMap, time::Instant}; use sysinfo::{System, SystemExt}; +use futures::join; + pub mod cpu; pub mod disks; pub mod mem; diff --git a/src/app/data_harvester/processes.rs b/src/app/data_harvester/processes.rs index 3c374bad..fe3fb750 100644 --- a/src/app/data_harvester/processes.rs +++ b/src/app/data_harvester/processes.rs @@ -232,9 +232,6 @@ pub fn get_sorted_processes_list( } *prev_pid_stats = new_pid_stats; - } else { - error!("Unable to properly parse CPU data in Linux."); - error!("Result: {:?}", cpu_calc.err()); } } else { let process_hashmap = sys.get_processes(); diff --git a/src/canvas.rs b/src/canvas.rs index eb534adb..8aaa1752 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -129,9 +129,7 @@ impl Painter { } } - // TODO: [REFACTOR] We should clean this up tbh // TODO: [FEATURE] Auto-resizing dialog sizes. - #[allow(clippy::cognitive_complexity)] pub fn draw_data( &mut self, terminal: &mut Terminal, app_state: &mut app::App, ) -> error::Result<()> { @@ -139,9 +137,6 @@ impl Painter { let current_height = terminal_size.height; let current_width = terminal_size.width; - // TODO: [OPT] we might be able to add an argument s.t. if there is - // no resize AND it's not a data update (or process refresh/search/etc.) - // then just... don't draw again! if self.height == 0 && self.width == 0 { self.height = current_height; self.width = current_width; @@ -336,7 +331,6 @@ impl Painter { ); } } else { - // TODO: [TUI] Change this back to a more even 33/33/34 when TUI releases let vertical_chunks = Layout::default() .direction(Direction::Vertical) .margin(1) diff --git a/src/canvas/canvas_colours/colour_utils.rs b/src/canvas/canvas_colours/colour_utils.rs index f55af29a..631aac7e 100644 --- a/src/canvas/canvas_colours/colour_utils.rs +++ b/src/canvas/canvas_colours/colour_utils.rs @@ -1,3 +1,4 @@ +use lazy_static::lazy_static; use std::collections::HashMap; use tui::style::{Color, Style}; diff --git a/src/canvas/widgets/cpu_graph.rs b/src/canvas/widgets/cpu_graph.rs index bbc25166..19abd62c 100644 --- a/src/canvas/widgets/cpu_graph.rs +++ b/src/canvas/widgets/cpu_graph.rs @@ -1,3 +1,4 @@ +use lazy_static::lazy_static; use std::borrow::Cow; use std::cmp::max; @@ -45,31 +46,31 @@ impl CpuGraphWidget for Painter { let cpu_data: &[ConvertedCpuData] = &app_state.canvas_data.cpu_data; let display_time_labels = [ - format!("{}s", app_state.cpu_state.display_time / 1000), + format!("{}s", app_state.cpu_state.current_display_time / 1000), "0s".to_string(), ]; let x_axis = if app_state.app_config_fields.hide_time || (app_state.app_config_fields.autohide_time - && app_state.cpu_state.display_time_instant.is_none()) + && app_state.cpu_state.autohide_timer.is_none()) { - Axis::default().bounds([0.0, app_state.cpu_state.display_time as f64]) - } else if let Some(time) = app_state.cpu_state.display_time_instant { + Axis::default().bounds([0.0, app_state.cpu_state.current_display_time as f64]) + } else if let Some(time) = app_state.cpu_state.autohide_timer { if std::time::Instant::now().duration_since(time).as_millis() < AUTOHIDE_TIMEOUT_MILLISECONDS as u128 { Axis::default() - .bounds([0.0, app_state.cpu_state.display_time as f64]) + .bounds([0.0, app_state.cpu_state.current_display_time as f64]) .style(self.colours.graph_style) .labels_style(self.colours.graph_style) .labels(&display_time_labels) } else { - app_state.cpu_state.display_time_instant = None; - Axis::default().bounds([0.0, app_state.cpu_state.display_time as f64]) + app_state.cpu_state.autohide_timer = None; + Axis::default().bounds([0.0, app_state.cpu_state.current_display_time as f64]) } } else { Axis::default() - .bounds([0.0, app_state.cpu_state.display_time as f64]) + .bounds([0.0, app_state.cpu_state.current_display_time as f64]) .style(self.colours.graph_style) .labels_style(self.colours.graph_style) .labels(&display_time_labels) diff --git a/src/canvas/widgets/disk_table.rs b/src/canvas/widgets/disk_table.rs index 59d2c99f..2e4dbd2a 100644 --- a/src/canvas/widgets/disk_table.rs +++ b/src/canvas/widgets/disk_table.rs @@ -1,5 +1,5 @@ +use lazy_static::lazy_static; use std::cmp::max; - use tui::{ backend::Backend, layout::{Constraint, Direction, Layout, Rect}, diff --git a/src/canvas/widgets/mem_graph.rs b/src/canvas/widgets/mem_graph.rs index d30e88e3..61b7ff72 100644 --- a/src/canvas/widgets/mem_graph.rs +++ b/src/canvas/widgets/mem_graph.rs @@ -27,30 +27,30 @@ impl MemGraphWidget for Painter { let swap_data: &[(f64, f64)] = &app_state.canvas_data.swap_data; let display_time_labels = [ - format!("{}s", app_state.mem_state.display_time / 1000), + format!("{}s", app_state.mem_state.current_display_time / 1000), "0s".to_string(), ]; let x_axis = if app_state.app_config_fields.hide_time || (app_state.app_config_fields.autohide_time - && app_state.mem_state.display_time_instant.is_none()) + && app_state.mem_state.autohide_timer.is_none()) { - Axis::default().bounds([0.0, app_state.mem_state.display_time as f64]) - } else if let Some(time) = app_state.mem_state.display_time_instant { + Axis::default().bounds([0.0, app_state.mem_state.current_display_time as f64]) + } else if let Some(time) = app_state.mem_state.autohide_timer { if std::time::Instant::now().duration_since(time).as_millis() < AUTOHIDE_TIMEOUT_MILLISECONDS as u128 { Axis::default() - .bounds([0.0, app_state.mem_state.display_time as f64]) + .bounds([0.0, app_state.mem_state.current_display_time as f64]) .style(self.colours.graph_style) .labels_style(self.colours.graph_style) .labels(&display_time_labels) } else { - app_state.mem_state.display_time_instant = None; - Axis::default().bounds([0.0, app_state.mem_state.display_time as f64]) + app_state.mem_state.autohide_timer = None; + Axis::default().bounds([0.0, app_state.mem_state.current_display_time as f64]) } } else { Axis::default() - .bounds([0.0, app_state.mem_state.display_time as f64]) + .bounds([0.0, app_state.mem_state.current_display_time as f64]) .style(self.colours.graph_style) .labels_style(self.colours.graph_style) .labels(&display_time_labels) diff --git a/src/canvas/widgets/network_graph.rs b/src/canvas/widgets/network_graph.rs index f8abff28..206c9cd4 100644 --- a/src/canvas/widgets/network_graph.rs +++ b/src/canvas/widgets/network_graph.rs @@ -1,3 +1,4 @@ +use lazy_static::lazy_static; use std::cmp::max; use crate::{ @@ -40,30 +41,30 @@ impl NetworkGraphWidget for Painter { let network_data_tx: &[(f64, f64)] = &app_state.canvas_data.network_data_tx; let display_time_labels = [ - format!("{}s", app_state.net_state.display_time / 1000), + format!("{}s", app_state.net_state.current_display_time / 1000), "0s".to_string(), ]; let x_axis = if app_state.app_config_fields.hide_time || (app_state.app_config_fields.autohide_time - && app_state.net_state.display_time_instant.is_none()) + && app_state.net_state.autohide_timer.is_none()) { - Axis::default().bounds([0.0, app_state.net_state.display_time as f64]) - } else if let Some(time) = app_state.net_state.display_time_instant { + Axis::default().bounds([0.0, app_state.net_state.current_display_time as f64]) + } else if let Some(time) = app_state.net_state.autohide_timer { if std::time::Instant::now().duration_since(time).as_millis() < AUTOHIDE_TIMEOUT_MILLISECONDS as u128 { Axis::default() - .bounds([0.0, app_state.net_state.display_time as f64]) + .bounds([0.0, app_state.net_state.current_display_time as f64]) .style(self.colours.graph_style) .labels_style(self.colours.graph_style) .labels(&display_time_labels) } else { - app_state.net_state.display_time_instant = None; - Axis::default().bounds([0.0, app_state.net_state.display_time as f64]) + app_state.net_state.autohide_timer = None; + Axis::default().bounds([0.0, app_state.net_state.current_display_time as f64]) } } else { Axis::default() - .bounds([0.0, app_state.net_state.display_time as f64]) + .bounds([0.0, app_state.net_state.current_display_time as f64]) .style(self.colours.graph_style) .labels_style(self.colours.graph_style) .labels(&display_time_labels) diff --git a/src/canvas/widgets/temp_table.rs b/src/canvas/widgets/temp_table.rs index 6381c12f..5ce3228f 100644 --- a/src/canvas/widgets/temp_table.rs +++ b/src/canvas/widgets/temp_table.rs @@ -1,3 +1,4 @@ +use lazy_static::lazy_static; use std::cmp::max; use tui::{ diff --git a/src/constants.rs b/src/constants.rs index da98c16f..e4c274b4 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,3 +1,5 @@ +use lazy_static::lazy_static; + // How long to store data. pub const STALE_MAX_MILLISECONDS: u64 = 600 * 1000; // Keep 10 minutes of data. diff --git a/src/data_conversion.rs b/src/data_conversion.rs index 15160a8d..93473a2a 100644 --- a/src/data_conversion.rs +++ b/src/data_conversion.rs @@ -272,7 +272,7 @@ pub fn get_rx_tx_data_points( current_data.current_instant }; - // TODO: [REFACTOR] Can we use combine on this, CPU, and MEM? + // TODO: [REFACTOR] Can we use collect on this, CPU, and MEM? for (time, data) in ¤t_data.timed_data_vec { let time_from_start: f64 = (display_time as f64 - current_time.duration_since(*time).as_millis() as f64).floor(); diff --git a/src/main.rs b/src/main.rs index ec194e24..972237bf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,5 @@ #![warn(rust_2018_idioms)] -#[macro_use] -extern crate clap; -#[macro_use] -extern crate futures; -#[macro_use] -extern crate lazy_static; #[macro_use] extern crate log; @@ -18,6 +12,8 @@ use std::{ time::{Duration, Instant}, }; +use clap::*; + use crossterm::{ event::{ poll, read, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent, @@ -109,42 +105,14 @@ fn main() -> error::Result<()> { let config: Config = create_config(matches.value_of("CONFIG_LOCATION"))?; - let update_rate_in_milliseconds: u64 = - get_update_rate_in_milliseconds(&matches.value_of("RATE_MILLIS"), &config)?; - - // Set other settings - let temperature_type = get_temperature_option(&matches, &config)?; - let show_average_cpu = get_avg_cpu_option(&matches, &config); - let use_dot = get_use_dot_option(&matches, &config); - let left_legend = get_use_left_legend_option(&matches, &config); - let use_current_cpu_total = get_use_current_cpu_total_option(&matches, &config); - let current_widget_selected = get_default_widget(&matches, &config); - let show_disabled_data = get_show_disabled_data_option(&matches, &config); - let use_basic_mode = get_use_basic_mode_option(&matches, &config); - let default_time_value = get_default_time_value_option(&matches, &config)?; - let time_interval = get_time_interval_option(&matches, &config)?; - // Create "app" struct, which will control most of the program and store settings/state - let mut app = App::new( - show_average_cpu, - temperature_type, - update_rate_in_milliseconds, - use_dot, - left_legend, - use_current_cpu_total, - current_widget_selected, - show_disabled_data, - use_basic_mode, - default_time_value, - time_interval, - ); + let mut app = build_app(&matches, &config)?; + // TODO: [REFACTOR] Change this enable_app_grouping(&matches, &config, &mut app); enable_app_case_sensitive(&matches, &config, &mut app); enable_app_match_whole_word(&matches, &config, &mut app); enable_app_use_regex(&matches, &config, &mut app); - enable_hide_time(&matches, &config, &mut app); - enable_autohide_time(&matches, &config, &mut app); // Set up up tui and crossterm let mut stdout_val = stdout(); @@ -166,7 +134,7 @@ fn main() -> error::Result<()> { let tx = tx.clone(); thread::spawn(move || loop { thread::sleep(Duration::from_millis( - constants::STALE_MAX_MILLISECONDS as u64 + 5000, + constants::STALE_MAX_MILLISECONDS + 5000, )); tx.send(BottomEvent::Clean).unwrap(); }); @@ -176,8 +144,8 @@ fn main() -> error::Result<()> { create_event_thread( tx, rrx, - use_current_cpu_total, - update_rate_in_milliseconds as u64, + app.app_config_fields.use_current_cpu_total, + app.app_config_fields.update_rate_in_milliseconds, app.app_config_fields.temperature_type.clone(), app.app_config_fields.show_average_cpu, ); @@ -213,7 +181,7 @@ fn main() -> error::Result<()> { // Network let network_data = convert_network_data_points( &app.data_collection, - app.net_state.display_time, + app.net_state.current_display_time, false, ); app.canvas_data.network_data_rx = network_data.rx; @@ -231,12 +199,12 @@ fn main() -> error::Result<()> { // Memory app.canvas_data.mem_data = convert_mem_data_points( &app.data_collection, - app.mem_state.display_time, + app.mem_state.current_display_time, false, ); app.canvas_data.swap_data = convert_swap_data_points( &app.data_collection, - app.mem_state.display_time, + app.mem_state.current_display_time, false, ); let memory_and_swap_labels = convert_mem_labels(&app.data_collection); @@ -254,7 +222,7 @@ fn main() -> error::Result<()> { // CPU app.canvas_data.cpu_data = convert_cpu_data_points( &app.data_collection, - app.cpu_state.display_time, + app.cpu_state.current_display_time, false, ); @@ -590,7 +558,7 @@ fn handle_force_redraws(app: &mut App) { if app.cpu_state.force_update { app.canvas_data.cpu_data = convert_cpu_data_points( &app.data_collection, - app.cpu_state.display_time, + app.cpu_state.current_display_time, app.is_frozen, ); app.cpu_state.force_update = false; @@ -599,12 +567,12 @@ fn handle_force_redraws(app: &mut App) { if app.mem_state.force_update { app.canvas_data.mem_data = convert_mem_data_points( &app.data_collection, - app.mem_state.display_time, + app.mem_state.current_display_time, app.is_frozen, ); app.canvas_data.swap_data = convert_swap_data_points( &app.data_collection, - app.mem_state.display_time, + app.mem_state.current_display_time, app.is_frozen, ); app.mem_state.force_update = false; @@ -613,7 +581,7 @@ fn handle_force_redraws(app: &mut App) { if app.net_state.force_update { let (rx, tx) = get_rx_tx_data_points( &app.data_collection, - app.net_state.display_time, + app.net_state.current_display_time, app.is_frozen, ); app.canvas_data.network_data_rx = rx; diff --git a/src/options.rs b/src/options.rs index b91be73a..b631f903 100644 --- a/src/options.rs +++ b/src/options.rs @@ -1,11 +1,17 @@ use serde::Deserialize; +use std::time::Instant; + use crate::{ - app::{data_harvester, App, WidgetPosition}, + app::{data_harvester, App, AppConfigFields, CpuState, MemState, NetState, WidgetPosition}, constants::*, utils::error::{self, BottomError}, }; +// use layout_manager::*; + +// mod layout_manager; + #[derive(Default, Deserialize)] pub struct Config { pub flags: Option, @@ -54,10 +60,64 @@ pub struct ConfigColours { pub graph_color: Option, } -pub fn get_update_rate_in_milliseconds( - update_rate: &Option<&str>, config: &Config, +pub fn build_app(matches: &clap::ArgMatches<'static>, config: &Config) -> error::Result { + let autohide_time = get_autohide_time(&matches, &config); + let default_time_value = get_default_time_value(&matches, &config)?; + let default_widget = get_default_widget(&matches, &config); + let use_basic_mode = get_use_basic_mode(&matches, &config); + + let current_widget_selected = if use_basic_mode { + match default_widget { + WidgetPosition::Cpu => WidgetPosition::BasicCpu, + WidgetPosition::Network => WidgetPosition::BasicNet, + WidgetPosition::Mem => WidgetPosition::BasicMem, + _ => default_widget, + } + } else { + default_widget + }; + + let previous_basic_table_selected = if default_widget.is_widget_table() { + default_widget + } else { + WidgetPosition::Process + }; + + let app_config_fields = AppConfigFields { + update_rate_in_milliseconds: get_update_rate_in_milliseconds(matches, config)?, + temperature_type: get_temperature(matches, config)?, + show_average_cpu: get_avg_cpu(matches, config), + use_dot: get_use_dot(matches, config), + left_legend: get_use_left_legend(matches, config), + use_current_cpu_total: get_use_current_cpu_total(matches, config), + show_disabled_data: get_show_disabled_data(matches, config), + use_basic_mode, + default_time_value, + time_interval: get_time_interval(matches, config)?, + hide_time: get_hide_time(matches, config), + autohide_time, + }; + + let time_now = if autohide_time { + Some(Instant::now()) + } else { + None + }; + + Ok(App::builder() + .app_config_fields(app_config_fields) + .current_widget_selected(current_widget_selected) + .previous_basic_table_selected(previous_basic_table_selected) + .cpu_state(CpuState::init(default_time_value, time_now)) + .mem_state(MemState::init(default_time_value, time_now)) + .net_state(NetState::init(default_time_value, time_now)) + .build()) +} + +fn get_update_rate_in_milliseconds( + matches: &clap::ArgMatches<'static>, config: &Config, ) -> error::Result { - let update_rate_in_milliseconds = if let Some(update_rate) = update_rate { + let update_rate_in_milliseconds = if let Some(update_rate) = matches.value_of("RATE_MILLIS") { update_rate.parse::()? } else if let Some(flags) = &config.flags { if let Some(rate) = flags.rate { @@ -82,7 +142,7 @@ pub fn get_update_rate_in_milliseconds( Ok(update_rate_in_milliseconds as u64) } -pub fn get_temperature_option( +fn get_temperature( matches: &clap::ArgMatches<'static>, config: &Config, ) -> error::Result { if matches.is_present("FAHRENHEIT") { @@ -109,7 +169,7 @@ pub fn get_temperature_option( Ok(data_harvester::temperature::TemperatureType::Celsius) } -pub fn get_avg_cpu_option(matches: &clap::ArgMatches<'static>, config: &Config) -> bool { +fn get_avg_cpu(matches: &clap::ArgMatches<'static>, config: &Config) -> bool { if matches.is_present("AVG_CPU") { return true; } else if let Some(flags) = &config.flags { @@ -121,7 +181,7 @@ pub fn get_avg_cpu_option(matches: &clap::ArgMatches<'static>, config: &Config) false } -pub fn get_use_dot_option(matches: &clap::ArgMatches<'static>, config: &Config) -> bool { +fn get_use_dot(matches: &clap::ArgMatches<'static>, config: &Config) -> bool { if matches.is_present("DOT_MARKER") { return true; } else if let Some(flags) = &config.flags { @@ -132,7 +192,7 @@ pub fn get_use_dot_option(matches: &clap::ArgMatches<'static>, config: &Config) false } -pub fn get_use_left_legend_option(matches: &clap::ArgMatches<'static>, config: &Config) -> bool { +fn get_use_left_legend(matches: &clap::ArgMatches<'static>, config: &Config) -> bool { if matches.is_present("LEFT_LEGEND") { return true; } else if let Some(flags) = &config.flags { @@ -144,9 +204,7 @@ pub fn get_use_left_legend_option(matches: &clap::ArgMatches<'static>, config: & false } -pub fn get_use_current_cpu_total_option( - matches: &clap::ArgMatches<'static>, config: &Config, -) -> bool { +fn get_use_current_cpu_total(matches: &clap::ArgMatches<'static>, config: &Config) -> bool { if matches.is_present("USE_CURR_USAGE") { return true; } else if let Some(flags) = &config.flags { @@ -158,7 +216,7 @@ pub fn get_use_current_cpu_total_option( false } -pub fn get_show_disabled_data_option(matches: &clap::ArgMatches<'static>, config: &Config) -> bool { +fn get_show_disabled_data(matches: &clap::ArgMatches<'static>, config: &Config) -> bool { if matches.is_present("SHOW_DISABLED_DATA") { return true; } else if let Some(flags) = &config.flags { @@ -170,7 +228,7 @@ pub fn get_show_disabled_data_option(matches: &clap::ArgMatches<'static>, config false } -pub fn get_use_basic_mode_option(matches: &clap::ArgMatches<'static>, config: &Config) -> bool { +fn get_use_basic_mode(matches: &clap::ArgMatches<'static>, config: &Config) -> bool { if matches.is_present("BASIC_MODE") { return true; } else if let Some(flags) = &config.flags { @@ -182,7 +240,7 @@ pub fn get_use_basic_mode_option(matches: &clap::ArgMatches<'static>, config: &C false } -pub fn get_default_time_value_option( +fn get_default_time_value( matches: &clap::ArgMatches<'static>, config: &Config, ) -> error::Result { let default_time = if let Some(default_time_value) = matches.value_of("DEFAULT_TIME_VALUE") { @@ -202,17 +260,16 @@ pub fn get_default_time_value_option( "Please set your default value to be at least 30000 milliseconds.".to_string(), )); } else if default_time as u128 > STALE_MAX_MILLISECONDS as u128 { - return Err(BottomError::InvalidArg( - format!("Please set your default value to be at most {} milliseconds.", STALE_MAX_MILLISECONDS), - )); + return Err(BottomError::InvalidArg(format!( + "Please set your default value to be at most {} milliseconds.", + STALE_MAX_MILLISECONDS + ))); } Ok(default_time as u64) } -pub fn get_time_interval_option( - matches: &clap::ArgMatches<'static>, config: &Config, -) -> error::Result { +fn get_time_interval(matches: &clap::ArgMatches<'static>, config: &Config) -> error::Result { let time_interval = if let Some(time_interval) = matches.value_of("TIME_DELTA") { time_interval.parse::()? } else if let Some(flags) = &config.flags { @@ -230,9 +287,10 @@ pub fn get_time_interval_option( "Please set your time delta to be at least 1000 milliseconds.".to_string(), )); } else if time_interval > STALE_MAX_MILLISECONDS as u128 { - return Err(BottomError::InvalidArg( - format!("Please set your time delta to be at most {} milliseconds.", STALE_MAX_MILLISECONDS), - )); + return Err(BottomError::InvalidArg(format!( + "Please set your time delta to be at most {} milliseconds.", + STALE_MAX_MILLISECONDS + ))); } Ok(time_interval as u64) @@ -290,39 +348,34 @@ pub fn enable_app_use_regex(matches: &clap::ArgMatches<'static>, config: &Config } } -pub fn enable_hide_time(matches: &clap::ArgMatches<'static>, config: &Config, app: &mut App) { +fn get_hide_time(matches: &clap::ArgMatches<'static>, config: &Config) -> bool { if matches.is_present("HIDE_TIME") { - app.app_config_fields.hide_time = true; + return true; } else if let Some(flags) = &config.flags { if let Some(hide_time) = flags.hide_time { if hide_time { - app.app_config_fields.hide_time = true; + return true; } } } + false } -pub fn enable_autohide_time(matches: &clap::ArgMatches<'static>, config: &Config, app: &mut App) { +fn get_autohide_time(matches: &clap::ArgMatches<'static>, config: &Config) -> bool { if matches.is_present("AUTOHIDE_TIME") { - app.app_config_fields.autohide_time = true; - let time = Some(std::time::Instant::now()); - app.cpu_state.display_time_instant = time; - app.mem_state.display_time_instant = time; - app.net_state.display_time_instant = time; + return true; } else if let Some(flags) = &config.flags { if let Some(autohide_time) = flags.autohide_time { if autohide_time { - app.app_config_fields.autohide_time = true; - let time = Some(std::time::Instant::now()); - app.cpu_state.display_time_instant = time; - app.mem_state.display_time_instant = time; - app.net_state.display_time_instant = time; + return true; } } } + + false } -pub fn get_default_widget(matches: &clap::ArgMatches<'static>, config: &Config) -> WidgetPosition { +fn get_default_widget(matches: &clap::ArgMatches<'static>, config: &Config) -> WidgetPosition { if matches.is_present("CPU_WIDGET") { return WidgetPosition::Cpu; } else if matches.is_present("MEM_WIDGET") { diff --git a/src/options/layout_manager.rs b/src/options/layout_manager.rs new file mode 100644 index 00000000..d13ac5f9 --- /dev/null +++ b/src/options/layout_manager.rs @@ -0,0 +1,3 @@ +use serde::Deserialize; +use toml::Value; +