diff --git a/CHANGELOG.md b/CHANGELOG.md index c4b54cff..dc363afe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `mem_as_value` is now `process_memory_as_value`. - [#1472](https://github.com/ClementTsang/bottom/pull/1472): The following config fields have changed names: - `mem_as_value` is now `process_memory_as_value`. +- [#1481](https://github.com/ClementTsang/bottom/pull/1481): The following config fields have changed names: + - `disk_filter` is now `disk.name_filter`. + - `mount_filter` is now `disk.mount_filter`. + - `temp_filter` is now `temperature.sensor_filter` + - `net_filter` is now `network.interface_filter` ### Bug Fixes diff --git a/Cargo.toml b/Cargo.toml index 6714ce0a..97eda9f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -143,6 +143,7 @@ clap_complete_nushell = "4.5.1" clap_complete_fig = "4.5.0" clap_mangen = "0.2.20" indoc = "2.0.5" +# schemars = "0.8.21" [package.metadata.deb] section = "utility" diff --git a/sample_configs/default_config.toml b/sample_configs/default_config.toml index 4c8bd8ce..4855b008 100644 --- a/sample_configs/default_config.toml +++ b/sample_configs/default_config.toml @@ -80,18 +80,53 @@ # How much data is stored at once in terms of time. #retention = "10m" -# These are flags around the process widget. - +# Processes widget configuration #[processes] +# The columns shown by the process widget. The following columns are supported: +# PID, Name, CPU%, Mem%, R/s, W/s, T.Read, T.Write, User, State, Time, GMem%, GPU% #columns = ["PID", "Name", "CPU%", "Mem%", "R/s", "W/s", "T.Read", "T.Write", "User", "State", "GMEM%", "GPU%"] -# [cpu] +# CPU widget configuration +#[cpu] # One of "all" (default), "average"/"avg" # default = "average" +# Disk widget configuration +#[disk] +#[disk.name_filter] +#is_list_ignored = true +#list = ["/dev/sda\\d+", "/dev/nvme0n1p2"] +#regex = true +#case_sensitive = false +#whole_word = false + +#[disk.mount_filter] +#is_list_ignored = true +#list = ["/mnt/.*", "/boot"] +#regex = true +#case_sensitive = false +#whole_word = false + +# Temperature widget configuration +#[temperature] +#[temperature.sensor_filter] +#is_list_ignored = true +#list = ["cpu", "wifi"] +#regex = false +#case_sensitive = false +#whole_word = false + +# Network widget configuration +#[network] +#[network.interface_filter] +#is_list_ignored = true +#list = ["virbr0.*"] +#regex = true +#case_sensitive = false +#whole_word = false + # These are all the components that support custom theming. Note that colour support # will depend on terminal support. - #[colors] # Uncomment if you want to use custom colors # Represents the colour of table headers (processes, CPU, disks, temperature). #table_header_color="LightBlue" @@ -160,33 +195,3 @@ # [[row.child]] # type="proc" # default=true - -# Filters - you can hide specific temperature sensors, network interfaces, and disks using filters. This is admittedly -# a bit hard to use as of now, and there is a planned in-app interface for managing this in the future: -#[disk_filter] -#is_list_ignored = true -#list = ["/dev/sda\\d+", "/dev/nvme0n1p2"] -#regex = true -#case_sensitive = false -#whole_word = false - -#[mount_filter] -#is_list_ignored = true -#list = ["/mnt/.*", "/boot"] -#regex = true -#case_sensitive = false -#whole_word = false - -#[temp_filter] -#is_list_ignored = true -#list = ["cpu", "wifi"] -#regex = false -#case_sensitive = false -#whole_word = false - -#[net_filter] -#is_list_ignored = true -#list = ["virbr0.*"] -#regex = true -#case_sensitive = false -#whole_word = false diff --git a/src/app.rs b/src/app.rs index 44976346..ef3fb280 100644 --- a/src/app.rs +++ b/src/app.rs @@ -421,7 +421,8 @@ impl App { pws.is_sort_open = !pws.is_sort_open; pws.force_rerender = true; - // If the sort is now open, move left. Otherwise, if the proc sort was selected, force move right. + // If the sort is now open, move left. Otherwise, if the proc sort was selected, + // force move right. if pws.is_sort_open { pws.sort_table.set_position(pws.table.sort_index()); self.move_widget_selection(&WidgetDirection::Left); @@ -1054,13 +1055,15 @@ impl App { .widget_states .get_mut(&(self.current_widget.widget_id - 1)) { - // Traverse backwards from the current cursor location until you hit non-whitespace characters, - // then continue to traverse (and delete) backwards until you hit a whitespace character. Halt. + // Traverse backwards from the current cursor location until you hit + // non-whitespace characters, then continue to traverse (and + // delete) backwards until you hit a whitespace character. Halt. // So... first, let's get our current cursor position in terms of char indices. let end_index = proc_widget_state.cursor_char_index(); - // Then, let's crawl backwards until we hit our location, and store the "head"... + // Then, let's crawl backwards until we hit our location, and store the + // "head"... let query = proc_widget_state.current_search_query(); let mut start_index = 0; let mut saw_non_whitespace = false; @@ -1617,7 +1620,8 @@ impl App { if let Some(basic_table_widget_state) = &mut self.states.basic_table_widget_state { - // We also want to move towards Proc if we had set it to ProcSort. + // We also want to move towards Proc if we had set it to + // ProcSort. if let BottomWidgetType::ProcSort = basic_table_widget_state.currently_displayed_widget_type { @@ -2505,20 +2509,22 @@ impl App { } } - /// Moves the mouse to the widget that was clicked on, then propagates the click down to be - /// handled by the widget specifically. + /// Moves the mouse to the widget that was clicked on, then propagates the + /// click down to be handled by the widget specifically. pub fn on_left_mouse_up(&mut self, x: u16, y: u16) { - // Pretty dead simple - iterate through the widget map and go to the widget where the click - // is within. + // Pretty dead simple - iterate through the widget map and go to the widget + // where the click is within. // TODO: [REFACTOR] might want to refactor this, it's really ugly. - // TODO: [REFACTOR] Might wanna refactor ALL state things in general, currently everything - // is grouped up as an app state. We should separate stuff like event state and gui state and etc. + // TODO: [REFACTOR] Might wanna refactor ALL state things in general, currently + // everything is grouped up as an app state. We should separate stuff + // like event state and gui state and etc. - // TODO: [MOUSE] double click functionality...? We would do this above all other actions and SC if needed. + // TODO: [MOUSE] double click functionality...? We would do this above all + // other actions and SC if needed. - // Short circuit if we're in basic table... we might have to handle the basic table arrow - // case here... + // Short circuit if we're in basic table... we might have to handle the basic + // table arrow case here... if let Some(bt) = &mut self.states.basic_table_widget_state { if let ( @@ -2582,8 +2588,8 @@ impl App { } } - // Second short circuit --- are we in the dd dialog state? If so, only check yes/no/signals - // and bail after. + // Second short circuit --- are we in the dd dialog state? If so, only check + // yes/no/signals and bail after. if self.is_in_dialog() { match self.delete_dialog_state.button_positions.iter().find( |(tl_x, tl_y, br_x, br_y, _idx)| { @@ -2649,7 +2655,8 @@ impl App { ) { let border_offset = u16::from(self.is_drawing_border()); - // This check ensures the click isn't actually just clicking on the bottom border. + // This check ensures the click isn't actually just clicking on the bottom + // border. if y < (brc_y - border_offset) { match &self.current_widget.widget_type { BottomWidgetType::Proc @@ -2682,8 +2689,10 @@ impl App { self.change_process_position(change); - // If in tree mode, also check to see if this click is on - // the same entry as the already selected one - if it is, + // If in tree mode, also check to see if this click is + // on + // the same entry as the already selected one - if it + // is, // then we minimize. if is_tree_mode && change == 0 { self.toggle_collapsing_process_branch(); @@ -2755,8 +2764,9 @@ impl App { _ => {} } } else { - // We might have clicked on a header! Check if we only exceeded the table + border offset, and - // it's implied we exceeded the gap offset. + // We might have clicked on a header! Check if we only exceeded the + // table + border offset, and it's implied + // we exceeded the gap offset. if clicked_entry == border_offset { match &self.current_widget.widget_type { BottomWidgetType::Proc => { @@ -2851,8 +2861,9 @@ impl App { /// A quick and dirty way to handle paste events. pub fn handle_paste(&mut self, paste: String) { - // Partially copy-pasted from the single-char variant; should probably clean up this process in the future. - // In particular, encapsulate this entire logic and add some tests to make it less potentially error-prone. + // Partially copy-pasted from the single-char variant; should probably clean up + // this process in the future. In particular, encapsulate this entire + // logic and add some tests to make it less potentially error-prone. let is_in_search_widget = self.is_in_search_widget(); if let Some(proc_widget_state) = self .states diff --git a/src/app/data_farmer.rs b/src/app/data_farmer.rs index 104778d4..f756862a 100644 --- a/src/app/data_farmer.rs +++ b/src/app/data_farmer.rs @@ -2,10 +2,10 @@ //! a better name for the file. Since I called data collection "harvesting", //! then this is the farmer I guess. //! -//! Essentially the main goal is to shift the initial calculation and distribution -//! of joiner points and data to one central location that will only do it -//! *once* upon receiving the data --- as opposed to doing it on canvas draw, -//! which will be a costly process. +//! Essentially the main goal is to shift the initial calculation and +//! distribution of joiner points and data to one central location that will +//! only do it *once* upon receiving the data --- as opposed to doing it on +//! canvas draw, which will be a costly process. //! //! This will also handle the *cleaning* of stale data. That should be done //! in some manner (timer on another thread, some loop) that will occasionally @@ -102,8 +102,8 @@ impl ProcessData { /// collected, and what is needed to convert into a displayable form. /// /// If the app is *frozen* - that is, we do not want to *display* any changing -/// data, keep updating this. As of 2021-09-08, we just clone the current collection -/// when it freezes to have a snapshot floating around. +/// data, keep updating this. As of 2021-09-08, we just clone the current +/// collection when it freezes to have a snapshot floating around. /// /// Note that with this method, the *app* thread is responsible for cleaning - /// not the data collector. @@ -355,7 +355,8 @@ impl DataCollection { #[cfg(feature = "zfs")] { if !device.name.starts_with('/') { - Some(device.name.as_str()) // use the whole zfs dataset name + Some(device.name.as_str()) // use the whole zfs + // dataset name } else { device.name.split('/').last() } diff --git a/src/app/filter.rs b/src/app/filter.rs index 574fdab4..c313c0b6 100644 --- a/src/app/filter.rs +++ b/src/app/filter.rs @@ -16,8 +16,9 @@ impl Filter { #[inline] pub(crate) fn keep_entry(&self, value: &str) -> bool { if self.has_match(value) { - // If a match is found, then if we wanted to ignore if we match, return false. If we want - // to keep if we match, return true. Thus, return the inverse of `is_list_ignored`. + // If a match is found, then if we wanted to ignore if we match, return false. + // If we want to keep if we match, return true. Thus, return the + // inverse of `is_list_ignored`. !self.is_list_ignored } else { self.is_list_ignored diff --git a/src/app/frozen_state.rs b/src/app/frozen_state.rs index 6be62651..fc828311 100644 --- a/src/app/frozen_state.rs +++ b/src/app/frozen_state.rs @@ -1,7 +1,8 @@ use super::DataCollection; -/// The [`FrozenState`] indicates whether the application state should be frozen. It is either not frozen or -/// frozen and containing a copy of the state at the time. +/// The [`FrozenState`] indicates whether the application state should be +/// frozen. It is either not frozen or frozen and containing a copy of the state +/// at the time. pub enum FrozenState { NotFrozen, Frozen(Box<DataCollection>), diff --git a/src/app/process_killer.rs b/src/app/process_killer.rs index b3b198ee..3c2aaef7 100644 --- a/src/app/process_killer.rs +++ b/src/app/process_killer.rs @@ -1,4 +1,5 @@ -//! This file is meant to house (OS specific) implementations on how to kill processes. +//! This file is meant to house (OS specific) implementations on how to kill +//! processes. #[cfg(target_os = "windows")] use windows::Win32::{ @@ -61,7 +62,8 @@ pub fn kill_process_given_pid(pid: Pid) -> crate::utils::error::Result<()> { /// Kills a process, given a PID, for UNIX. #[cfg(target_family = "unix")] pub fn kill_process_given_pid(pid: Pid, signal: usize) -> crate::utils::error::Result<()> { - // SAFETY: the signal should be valid, and we act properly on an error (exit code not 0). + // SAFETY: the signal should be valid, and we act properly on an error (exit + // code not 0). let output = unsafe { libc::kill(pid, signal as i32) }; if output != 0 { diff --git a/src/app/query.rs b/src/app/query.rs index 5737cbc7..c642000a 100644 --- a/src/app/query.rs +++ b/src/app/query.rs @@ -26,13 +26,15 @@ const OR_LIST: [&str; 2] = ["or", "||"]; const AND_LIST: [&str; 2] = ["and", "&&"]; /// In charge of parsing the given query. -/// We are defining the following language for a query (case-insensitive prefixes): +/// We are defining the following language for a query (case-insensitive +/// prefixes): /// /// - Process names: No prefix required, can use regex, match word, or case. -/// Enclosing anything, including prefixes, in quotes, means we treat it as an entire process -/// rather than a prefix. +/// Enclosing anything, including prefixes, in quotes, means we treat it as an +/// entire process rather than a prefix. /// - PIDs: Use prefix `pid`, can use regex or match word (case is irrelevant). -/// - CPU: Use prefix `cpu`, cannot use r/m/c (regex, match word, case). Can compare. +/// - CPU: Use prefix `cpu`, cannot use r/m/c (regex, match word, case). Can +/// compare. /// - MEM: Use prefix `mem`, cannot use r/m/c. Can compare. /// - STATE: Use prefix `state`, can use regex, match word, or case. /// - USER: Use prefix `user`, can use regex, match word, or case. @@ -41,9 +43,10 @@ const AND_LIST: [&str; 2] = ["and", "&&"]; /// - Total read: Use prefix `read`. Can compare. /// - Total write: Use prefix `write`. Can compare. /// -/// For queries, whitespaces are our delimiters. We will merge together any adjacent non-prefixed -/// or quoted elements after splitting to treat as process names. -/// Furthermore, we want to support boolean joiners like AND and OR, and brackets. +/// For queries, whitespaces are our delimiters. We will merge together any +/// adjacent non-prefixed or quoted elements after splitting to treat as process +/// names. Furthermore, we want to support boolean joiners like AND and OR, and +/// brackets. pub fn parse_query( search_query: &str, is_searching_whole_word: bool, is_ignoring_case: bool, is_searching_with_regex: bool, @@ -176,8 +179,9 @@ pub fn parse_query( if let Some(queue_top) = query.pop_front() { if inside_quotation { if queue_top == "\"" { - // This means we hit something like "". Return an empty prefix, and to deal with - // the close quote checker, add one to the top of the stack. Ugly fix but whatever. + // This means we hit something like "". Return an empty prefix, and to deal + // with the close quote checker, add one to the top of the + // stack. Ugly fix but whatever. query.push_front("\"".to_string()); return Ok(Prefix { or: None, @@ -268,8 +272,9 @@ pub fn parse_query( } else if queue_top == ")" { return Err(QueryError("Missing opening parentheses".into())); } else if queue_top == "\"" { - // Similar to parentheses, trap and check for missing closing quotes. Note, however, that we - // will DIRECTLY call another process_prefix call... + // Similar to parentheses, trap and check for missing closing quotes. Note, + // however, that we will DIRECTLY call another process_prefix + // call... let prefix = process_prefix(query, true)?; if let Some(close_paren) = query.pop_front() { @@ -308,10 +313,12 @@ pub fn parse_query( // - (test) // - (test // - test) - // These are split into 2 to 3 different strings due to parentheses being + // These are split into 2 to 3 different strings due to + // parentheses being // delimiters in our query system. // - // Do we want these to be valid? They should, as a string, right? + // Do we want these to be valid? They should, as a string, + // right? return Ok(Prefix { or: None, @@ -385,8 +392,8 @@ pub fn parse_query( let mut condition: Option<QueryComparison> = None; let mut value: Option<f64> = None; - // TODO: Jeez, what the heck did I write here... add some tests and clean this up in the - // future. + // TODO: Jeez, what the heck did I write here... add some tests and + // clean this up in the future. if content == "=" { condition = Some(QueryComparison::Equal); if let Some(queue_next) = query.pop_front() { @@ -423,8 +430,9 @@ pub fn parse_query( if let Some(condition) = condition { if let Some(read_value) = value { - // Note that the values *might* have a unit or need to be parsed differently - // based on the prefix type! + // Note that the values *might* have a unit or need to be parsed + // differently based on the + // prefix type! let mut value = read_value; @@ -691,7 +699,8 @@ impl std::str::FromStr for PrefixType { } } -// TODO: This is also jank and could be better represented. Add tests, then clean up! +// TODO: This is also jank and could be better represented. Add tests, then +// clean up! #[derive(Default)] pub struct Prefix { pub or: Option<Box<Or>>, diff --git a/src/app/states.rs b/src/app/states.rs index 0088e53d..28ce85c8 100644 --- a/src/app/states.rs +++ b/src/app/states.rs @@ -112,7 +112,8 @@ impl Default for AppSearchState { } impl AppSearchState { - /// Resets the [`AppSearchState`] to its default state, albeit still enabled. + /// Resets the [`AppSearchState`] to its default state, albeit still + /// enabled. pub fn reset(&mut self) { *self = AppSearchState { is_enabled: self.is_enabled, @@ -161,7 +162,8 @@ impl AppSearchState { // Use the current index. start_index } else if cursor_range.end >= available_width { - // If the current position is past the last visible element, skip until we see it. + // If the current position is past the last visible element, skip until we + // see it. let mut index = 0; for i in 0..(cursor_index + 1) { @@ -211,7 +213,8 @@ impl AppSearchState { Ok(_) => {} Err(err) => match err { GraphemeIncomplete::PreContext(ctx) => { - // Provide the entire string as context. Not efficient but should resolve failures. + // Provide the entire string as context. Not efficient but should resolve + // failures. self.grapheme_cursor .provide_context(&self.current_search_query[0..ctx], 0); @@ -233,7 +236,8 @@ impl AppSearchState { Ok(_) => {} Err(err) => match err { GraphemeIncomplete::PreContext(ctx) => { - // Provide the entire string as context. Not efficient but should resolve failures. + // Provide the entire string as context. Not efficient but should resolve + // failures. self.grapheme_cursor .provide_context(&self.current_search_query[0..ctx], 0); diff --git a/src/canvas.rs b/src/canvas.rs index ca144c15..9b587b32 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -260,7 +260,8 @@ 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: [REFACTOR] The point we start changing size at currently hard-coded + // in. [ Constraint::Percentage(0), Constraint::Percentage(100), @@ -386,7 +387,8 @@ impl Painter { let actual_cpu_data_len = app_state.converted_data.cpu_data.len().saturating_sub(1); - // This fixes #397, apparently if the height is 1, it can't render the CPU bars... + // This fixes #397, apparently if the height is 1, it can't render the CPU + // bars... let cpu_height = { let c = (actual_cpu_data_len / 4) as u16 + u16::from(actual_cpu_data_len % 4 != 0); @@ -499,15 +501,15 @@ impl Painter { } if self.derived_widget_draw_locs.is_empty() || app_state.is_force_redraw { - // TODO: Can I remove this? Does ratatui's layout constraints work properly for fixing - // https://github.com/ClementTsang/bottom/issues/896 now? + // TODO: Can I remove this? Does ratatui's layout constraints work properly for + // fixing https://github.com/ClementTsang/bottom/issues/896 now? fn get_constraints( direction: Direction, constraints: &[LayoutConstraint], area: Rect, ) -> Vec<Rect> { // Order of operations: // - Ratios first + canvas-handled (which is just zero) - // - Then any flex-grows to take up remaining space; divide amongst remaining - // hand out any remaining space + // - Then any flex-grows to take up remaining space; divide amongst + // remaining hand out any remaining space #[derive(Debug, Default, Clone, Copy)] struct Size { @@ -688,7 +690,8 @@ impl Painter { &col_rows.children ) .map(|(draw_loc, col_row_constraint_vec, widgets)| { - // Note that col_row_constraint_vec CONTAINS the widget constraints + // Note that col_row_constraint_vec CONTAINS the widget + // constraints let widget_draw_locs = get_constraints( Direction::Horizontal, col_row_constraint_vec.as_slice(), diff --git a/src/canvas/components/data_table.rs b/src/canvas/components/data_table.rs index f4e26f0c..ba92e077 100644 --- a/src/canvas/components/data_table.rs +++ b/src/canvas/components/data_table.rs @@ -20,13 +20,15 @@ use crate::utils::general::ClampExt; /// A [`DataTable`] is a component that displays data in a tabular form. /// -/// Note that [`DataTable`] takes a generic type `S`, bounded by [`SortType`]. This controls whether this table -/// expects sorted data or not, with two expected types: +/// Note that [`DataTable`] takes a generic type `S`, bounded by [`SortType`]. +/// This controls whether this table expects sorted data or not, with two +/// expected types: /// -/// - [`Unsortable`]: The default if otherwise not specified. This table does not expect sorted data. -/// - [`Sortable`]: This table expects sorted data, and there are helper functions to -/// facilitate things like sorting based on a selected column, shortcut column selection support, mouse column -/// selection support, etc. +/// - [`Unsortable`]: The default if otherwise not specified. This table does +/// not expect sorted data. +/// - [`Sortable`]: This table expects sorted data, and there are helper +/// functions to facilitate things like sorting based on a selected column, +/// shortcut column selection support, mouse column selection support, etc. pub struct DataTable<DataType, Header, S = Unsortable, C = Column<Header>> { pub columns: Vec<C>, pub state: DataTableState, @@ -89,8 +91,9 @@ impl<DataType: DataToCell<H>, H: ColumnHeader, S: SortType, C: DataTableColumn<H } } - /// Increments the scroll position if possible by a positive/negative offset. If there is a - /// valid change, this function will also return the new position wrapped in an [`Option`]. + /// Increments the scroll position if possible by a positive/negative + /// offset. If there is a valid change, this function will also return + /// the new position wrapped in an [`Option`]. pub fn increment_position(&mut self, change: i64) -> Option<usize> { let max_index = self.data.len(); let current_index = self.state.current_index; diff --git a/src/canvas/components/data_table/column.rs b/src/canvas/components/data_table/column.rs index 7bbfcbfa..556fbbbd 100644 --- a/src/canvas/components/data_table/column.rs +++ b/src/canvas/components/data_table/column.rs @@ -7,17 +7,20 @@ use std::{ /// A bound on the width of a column. #[derive(Clone, Copy, Debug)] pub enum ColumnWidthBounds { - /// A width of this type is as long as `desired`, but can otherwise shrink and grow up to a point. + /// A width of this type is as long as `desired`, but can otherwise shrink + /// and grow up to a point. Soft { - /// The desired, calculated width. Take this if possible as the base starting width. + /// The desired, calculated width. Take this if possible as the base + /// starting width. desired: u16, - /// The max width, as a percentage of the total width available. If [`None`], - /// then it can grow as desired. + /// The max width, as a percentage of the total width available. If + /// [`None`], then it can grow as desired. max_percentage: Option<f32>, }, - /// A width of this type is either as long as specified, or does not appear at all. + /// A width of this type is either as long as specified, or does not appear + /// at all. Hard(u16), /// A width of this type always resizes to the column header's text width. @@ -28,7 +31,8 @@ pub trait ColumnHeader { /// The "text" version of the column header. fn text(&self) -> Cow<'static, str>; - /// The version displayed when drawing the table. Defaults to [`ColumnHeader::text`]. + /// The version displayed when drawing the table. Defaults to + /// [`ColumnHeader::text`]. #[inline(always)] fn header(&self) -> Cow<'static, str> { self.text() @@ -63,8 +67,9 @@ pub trait DataTableColumn<H: ColumnHeader> { /// The actually displayed "header". fn header(&self) -> Cow<'static, str>; - /// The header length, along with any required additional lengths for things like arrows. - /// Defaults to getting the length of [`DataTableColumn::header`]. + /// The header length, along with any required additional lengths for things + /// like arrows. Defaults to getting the length of + /// [`DataTableColumn::header`]. fn header_len(&self) -> usize { self.header().len() } @@ -78,7 +83,8 @@ pub struct Column<H> { /// A restriction on this column's width. bounds: ColumnWidthBounds, - /// Marks that this column is currently "hidden", and should *always* be skipped. + /// Marks that this column is currently "hidden", and should *always* be + /// skipped. is_hidden: bool, } @@ -148,10 +154,13 @@ impl<H: ColumnHeader> Column<H> { } pub trait CalculateColumnWidths<H> { - /// Calculates widths for the columns of this table, given the current width when called. + /// Calculates widths for the columns of this table, given the current width + /// when called. /// - /// * `total_width` is the total width on the canvas that the columns can try and work with. - /// * `left_to_right` is whether to size from left-to-right (`true`) or right-to-left (`false`). + /// * `total_width` is the total width on the canvas that the columns can + /// try and work with. + /// * `left_to_right` is whether to size from left-to-right (`true`) or + /// right-to-left (`false`). fn calculate_column_widths(&self, total_width: u16, left_to_right: bool) -> Vec<NonZeroU16>; } diff --git a/src/canvas/components/data_table/data_type.rs b/src/canvas/components/data_table/data_type.rs index ebac3c07..c027d63e 100644 --- a/src/canvas/components/data_table/data_type.rs +++ b/src/canvas/components/data_table/data_type.rs @@ -9,8 +9,9 @@ pub trait DataToCell<H> where H: ColumnHeader, { - /// Given data, a column, and its corresponding width, return the string in the cell that will - /// be displayed in the [`DataTable`](super::DataTable). + /// Given data, a column, and its corresponding width, return the string in + /// the cell that will be displayed in the + /// [`DataTable`](super::DataTable). fn to_cell(&self, column: &H, calculated_width: NonZeroU16) -> Option<Cow<'static, str>>; /// Apply styling to the generated [`Row`] of cells. diff --git a/src/canvas/components/data_table/draw.rs b/src/canvas/components/data_table/draw.rs index 46304f83..0fe5bb91 100644 --- a/src/canvas/components/data_table/draw.rs +++ b/src/canvas/components/data_table/draw.rs @@ -202,7 +202,8 @@ where if !self.data.is_empty() || !self.first_draw { if self.first_draw { - self.first_draw = false; // TODO: Doing it this way is fine, but it could be done better (e.g. showing custom no results/entries message) + self.first_draw = false; // TODO: Doing it this way is fine, but it could be done better (e.g. showing + // custom no results/entries message) if let Some(first_index) = self.first_index { self.set_position(first_index); } diff --git a/src/canvas/components/data_table/sortable.rs b/src/canvas/components/data_table/sortable.rs index 1ece31cc..659fc939 100644 --- a/src/canvas/components/data_table/sortable.rs +++ b/src/canvas/components/data_table/sortable.rs @@ -46,7 +46,8 @@ pub struct Sortable { } /// The [`SortType`] trait is meant to be used in the typing of a [`DataTable`] -/// to denote whether the table is meant to display/store sorted or unsorted data. +/// to denote whether the table is meant to display/store sorted or unsorted +/// data. /// /// Note that the trait is [sealed](https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed), /// and therefore only [`Unsortable`] and [`Sortable`] can implement it. @@ -97,9 +98,10 @@ impl SortType for Sortable { SortOrder::Ascending => UP_ARROW, SortOrder::Descending => DOWN_ARROW, }; - // TODO: I think I can get away with removing the truncate_to_text call since - // I almost always bind to at least the header size... - // TODO: Or should we instead truncate but ALWAYS leave the arrow at the end? + // TODO: I think I can get away with removing the truncate_to_text call + // since I almost always bind to at least the header + // size... TODO: Or should we instead truncate but + // ALWAYS leave the arrow at the end? truncate_to_text(&concat_string!(c.header(), arrow), width.get()) } else { truncate_to_text(&c.header(), width.get()) @@ -127,7 +129,8 @@ pub struct SortColumn<T> { /// A restriction on this column's width. pub bounds: ColumnWidthBounds, - /// Marks that this column is currently "hidden", and should *always* be skipped. + /// Marks that this column is currently "hidden", and should *always* be + /// skipped. pub is_hidden: bool, } @@ -178,8 +181,9 @@ impl<D, T> SortColumn<T> where T: ColumnHeader + SortsRow<DataType = D>, { - /// Creates a new [`SortColumn`] with a width that follows the header width, which has no shortcut and sorts by - /// default in ascending order ([`SortOrder::Ascending`]). + /// Creates a new [`SortColumn`] with a width that follows the header width, + /// which has no shortcut and sorts by default in ascending order + /// ([`SortOrder::Ascending`]). pub fn new(inner: T) -> Self { Self { inner, @@ -189,8 +193,8 @@ where } } - /// Creates a new [`SortColumn`] with a hard width, which has no shortcut and sorts by default in - /// ascending order ([`SortOrder::Ascending`]). + /// Creates a new [`SortColumn`] with a hard width, which has no shortcut + /// and sorts by default in ascending order ([`SortOrder::Ascending`]). pub fn hard(inner: T, width: u16) -> Self { Self { inner, @@ -200,8 +204,8 @@ where } } - /// Creates a new [`SortColumn`] with a soft width, which has no shortcut and sorts by default in - /// ascending order ([`SortOrder::Ascending`]). + /// Creates a new [`SortColumn`] with a soft width, which has no shortcut + /// and sorts by default in ascending order ([`SortOrder::Ascending`]). pub fn soft(inner: T, max_percentage: Option<f32>) -> Self { Self { inner, @@ -226,7 +230,8 @@ where self } - /// Given a [`SortColumn`] and the sort order, sort a mutable slice of associated data. + /// Given a [`SortColumn`] and the sort order, sort a mutable slice of + /// associated data. pub fn sort_by(&self, data: &mut [D], order: SortOrder) { let descending = matches!(order, SortOrder::Descending); self.inner.sort_data(data, descending); @@ -284,11 +289,11 @@ where } } - /// Given some `x` and `y`, if possible, select the corresponding column or toggle the column if already selected, - /// and otherwise do nothing. + /// Given some `x` and `y`, if possible, select the corresponding column or + /// toggle the column if already selected, and otherwise do nothing. /// - /// If there was some update, the corresponding column type will be returned. If nothing happens, [`None`] is - /// returned. + /// If there was some update, the corresponding column type will be + /// returned. If nothing happens, [`None`] is returned. pub fn try_select_location(&mut self, x: u16, y: u16) -> Option<usize> { if self.state.inner_rect.height > 1 && self.state.inner_rect.y == y { if let Some(index) = self.get_range(x) { @@ -304,10 +309,11 @@ where /// Updates the sort index, and sets the sort order as appropriate. /// - /// If the index is different from the previous one, it will move to the new index and set the sort order - /// to the prescribed default sort order. + /// If the index is different from the previous one, it will move to the new + /// index and set the sort order to the prescribed default sort order. /// - /// If the index is the same as the previous one, it will simply toggle the current sort order. + /// If the index is the same as the previous one, it will simply toggle the + /// current sort order. pub fn set_sort_index(&mut self, index: usize) { if self.sort_type.sort_index == index { self.toggle_order(); diff --git a/src/canvas/components/time_graph.rs b/src/canvas/components/time_graph.rs index 361b502e..fa069760 100644 --- a/src/canvas/components/time_graph.rs +++ b/src/canvas/components/time_graph.rs @@ -23,7 +23,8 @@ pub struct GraphData<'a> { } pub struct TimeGraph<'a> { - /// The min and max x boundaries. Expects a f64 representing the time range in milliseconds. + /// The min and max x boundaries. Expects a f64 representing the time range + /// in milliseconds. pub x_bounds: [u64; 2], /// Whether to hide the time/x-labels. @@ -99,7 +100,8 @@ impl<'a> TimeGraph<'a> { ) } - /// Generates a title for the [`TimeGraph`] widget, given the available space. + /// Generates a title for the [`TimeGraph`] widget, given the available + /// space. fn generate_title(&self, draw_loc: Rect) -> Line<'_> { if self.is_expanded { let title_base = concat_string!(self.title, "── Esc to go back "); @@ -121,13 +123,15 @@ impl<'a> TimeGraph<'a> { } } - /// Draws a time graph at [`Rect`] location provided by `draw_loc`. A time graph is used to display data points - /// throughout time in the x-axis. + /// Draws a time graph at [`Rect`] location provided by `draw_loc`. A time + /// graph is used to display data points throughout time in the x-axis. /// /// This time graph: /// - Draws with the higher time value on the left, and lower on the right. - /// - Expects a [`TimeGraph`] to be passed in, which details how to draw the graph. - /// - Expects `graph_data`, which represents *what* data to draw, and various details like style and optional legends. + /// - Expects a [`TimeGraph`] to be passed in, which details how to draw the + /// graph. + /// - Expects `graph_data`, which represents *what* data to draw, and + /// various details like style and optional legends. pub fn draw_time_graph(&self, f: &mut Frame<'_>, draw_loc: Rect, graph_data: &[GraphData<'_>]) { let x_axis = self.generate_x_axis(); let y_axis = self.generate_y_axis(); diff --git a/src/canvas/components/tui_widget/pipe_gauge.rs b/src/canvas/components/tui_widget/pipe_gauge.rs index 08aa4ca2..f8905e9f 100644 --- a/src/canvas/components/tui_widget/pipe_gauge.rs +++ b/src/canvas/components/tui_widget/pipe_gauge.rs @@ -47,8 +47,8 @@ impl<'a> Default for PipeGauge<'a> { } impl<'a> PipeGauge<'a> { - /// The ratio, a value from 0.0 to 1.0 (any other greater or less will be clamped) - /// represents the portion of the pipe gauge to fill. + /// The ratio, a value from 0.0 to 1.0 (any other greater or less will be + /// clamped) represents the portion of the pipe gauge to fill. /// /// Note: passing in NaN will potentially cause problems. pub fn ratio(mut self, ratio: f64) -> Self { @@ -87,7 +87,8 @@ impl<'a> PipeGauge<'a> { self } - /// Whether to hide parts of the gauge/label if the inner label wouldn't fit. + /// Whether to hide parts of the gauge/label if the inner label wouldn't + /// fit. pub fn hide_parts(mut self, hide_parts: LabelLimit) -> Self { self.hide_parts = hide_parts; self diff --git a/src/canvas/components/tui_widget/time_chart.rs b/src/canvas/components/tui_widget/time_chart.rs index 05ec4374..922908d2 100644 --- a/src/canvas/components/tui_widget/time_chart.rs +++ b/src/canvas/components/tui_widget/time_chart.rs @@ -1,5 +1,5 @@ -//! A [`tui::widgets::Chart`] but slightly more specialized to show right-aligned timeseries -//! data. +//! A [`tui::widgets::Chart`] but slightly more specialized to show +//! right-aligned timeseries data. //! //! Generally should be updated to be in sync with [`chart.rs`](https://github.com/ratatui-org/ratatui/blob/main/src/widgets/chart.rs); //! the specializations are factored out to `time_chart/points.rs`. @@ -31,7 +31,8 @@ pub type Point = (f64, f64); pub struct Axis<'a> { /// Title displayed next to axis end pub(crate) title: Option<Line<'a>>, - /// Bounds for the axis (all data points outside these limits will not be represented) + /// Bounds for the axis (all data points outside these limits will not be + /// represented) pub(crate) bounds: [f64; 2], /// A list of labels to put to the left or below the axis pub(crate) labels: Option<Vec<Span<'a>>>, @@ -44,10 +45,11 @@ pub struct Axis<'a> { impl<'a> Axis<'a> { /// Sets the axis title /// - /// It will be displayed at the end of the axis. For an X axis this is the right, for a Y axis, - /// this is the top. + /// It will be displayed at the end of the axis. For an X axis this is the + /// right, for a Y axis, this is the top. /// - /// This is a fluent setter method which must be chained or used as it consumes self + /// This is a fluent setter method which must be chained or used as it + /// consumes self #[must_use = "method moves the value of self and returns the modified value"] pub fn title<T>(mut self, title: T) -> Axis<'a> where @@ -61,7 +63,8 @@ impl<'a> Axis<'a> { /// /// In other words, sets the min and max value on this axis. /// - /// This is a fluent setter method which must be chained or used as it consumes self + /// This is a fluent setter method which must be chained or used as it + /// consumes self #[must_use = "method moves the value of self and returns the modified value"] pub fn bounds(mut self, bounds: [f64; 2]) -> Axis<'a> { self.bounds = bounds; @@ -238,11 +241,13 @@ impl FromStr for LegendPosition { /// /// This is the main element composing a [`TimeChart`]. /// -/// A dataset can be [named](Dataset::name). Only named datasets will be rendered in the legend. +/// A dataset can be [named](Dataset::name). Only named datasets will be +/// rendered in the legend. /// -/// After that, you can pass it data with [`Dataset::data`]. Data is an array of `f64` tuples -/// (`(f64, f64)`), the first element being X and the second Y. It's also worth noting that, unlike -/// the [`Rect`], here the Y axis is bottom to top, as in math. +/// After that, you can pass it data with [`Dataset::data`]. Data is an array of +/// `f64` tuples (`(f64, f64)`), the first element being X and the second Y. +/// It's also worth noting that, unlike the [`Rect`], here the Y axis is bottom +/// to top, as in math. #[derive(Debug, Default, Clone, PartialEq)] pub struct Dataset<'a> { /// Name of the dataset (used in the legend if shown) @@ -270,12 +275,12 @@ impl<'a> Dataset<'a> { /// Sets the data points of this dataset /// - /// Points will then either be rendered as scrattered points or with lines between them - /// depending on [`Dataset::graph_type`]. + /// Points will then either be rendered as scrattered points or with lines + /// between them depending on [`Dataset::graph_type`]. /// - /// Data consist in an array of `f64` tuples (`(f64, f64)`), the first element being X and the - /// second Y. It's also worth noting that, unlike the [`Rect`], here the Y axis is bottom to - /// top, as in math. + /// Data consist in an array of `f64` tuples (`(f64, f64)`), the first + /// element being X and the second Y. It's also worth noting that, + /// unlike the [`Rect`], here the Y axis is bottom to top, as in math. #[must_use = "method moves the value of self and returns the modified value"] pub fn data(mut self, data: &'a [(f64, f64)]) -> Dataset<'a> { self.data = data; @@ -284,12 +289,15 @@ impl<'a> Dataset<'a> { /// Sets the kind of character to use to display this dataset /// - /// You can use dots (`•`), blocks (`█`), bars (`▄`), braille (`⠓`, `⣇`, `⣿`) or half-blocks - /// (`█`, `▄`, and `▀`). See [symbols::Marker] for more details. + /// You can use dots (`•`), blocks (`█`), bars (`▄`), braille (`⠓`, `⣇`, + /// `⣿`) or half-blocks (`█`, `▄`, and `▀`). See [symbols::Marker] for + /// more details. /// - /// Note [`Marker::Braille`] requires a font that supports Unicode Braille Patterns. + /// Note [`Marker::Braille`] requires a font that supports Unicode Braille + /// Patterns. /// - /// This is a fluent setter method which must be chained or used as it consumes self + /// This is a fluent setter method which must be chained or used as it + /// consumes self #[must_use = "method moves the value of self and returns the modified value"] pub fn marker(mut self, marker: symbols::Marker) -> Dataset<'a> { self.marker = marker; @@ -298,9 +306,10 @@ impl<'a> Dataset<'a> { /// Sets how the dataset should be drawn /// - /// [`TimeChart`] can draw either a [scatter](GraphType::Scatter) or [line](GraphType::Line) charts. - /// A scatter will draw only the points in the dataset while a line will also draw a line - /// between them. See [`GraphType`] for more details + /// [`TimeChart`] can draw either a [scatter](GraphType::Scatter) or + /// [line](GraphType::Line) charts. A scatter will draw only the points + /// in the dataset while a line will also draw a line between them. See + /// [`GraphType`] for more details #[must_use = "method moves the value of self and returns the modified value"] pub fn graph_type(mut self, graph_type: GraphType) -> Dataset<'a> { self.graph_type = graph_type; @@ -309,11 +318,13 @@ impl<'a> Dataset<'a> { /// Sets the style of this dataset /// - /// The given style will be used to draw the legend and the data points. Currently the legend - /// will use the entire style whereas the data points will only use the foreground. + /// The given style will be used to draw the legend and the data points. + /// Currently the legend will use the entire style whereas the data + /// points will only use the foreground. /// - /// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or - /// your own type that implements [`Into<Style>`]). + /// `style` accepts any type that is convertible to [`Style`] (e.g. + /// [`Style`], [`Color`], or your own type that implements + /// [`Into<Style>`]). #[must_use = "method moves the value of self and returns the modified value"] pub fn style<S: Into<Style>>(mut self, style: S) -> Dataset<'a> { self.style = style.into(); @@ -321,8 +332,8 @@ impl<'a> Dataset<'a> { } } -/// A container that holds all the infos about where to display each elements of the chart (axis, -/// labels, legend, ...). +/// A container that holds all the infos about where to display each elements of +/// the chart (axis, labels, legend, ...). #[derive(Debug, Default, Clone, Eq, PartialEq, Hash)] struct ChartLayout { /// Location of the title of the x axis @@ -343,8 +354,9 @@ struct ChartLayout { graph_area: Rect, } -/// A "custom" chart, just a slightly tweaked [`tui::widgets::Chart`] from ratatui, but with greater control over the -/// legend, and built with the idea of drawing data points relative to a time-based x-axis. +/// A "custom" chart, just a slightly tweaked [`tui::widgets::Chart`] from +/// ratatui, but with greater control over the legend, and built with the idea +/// of drawing data points relative to a time-based x-axis. /// /// Main changes: /// - Styling option for the legend box @@ -368,8 +380,8 @@ pub struct TimeChart<'a> { legend_style: Style, /// Constraints used to determine whether the legend should be shown or not hidden_legend_constraints: (Constraint, Constraint), - /// The position detnermine where the legenth is shown or hide regaurdless of - /// `hidden_legend_constraints` + /// The position detnermine where the legenth is shown or hide regaurdless + /// of `hidden_legend_constraints` legend_position: Option<LegendPosition>, /// The marker type. marker: Marker, @@ -402,8 +414,9 @@ impl<'a> TimeChart<'a> { /// Sets the style of the entire chart /// - /// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or - /// your own type that implements [`Into<Style>`]). + /// `style` accepts any type that is convertible to [`Style`] (e.g. + /// [`Style`], [`Color`], or your own type that implements + /// [`Into<Style>`]). /// /// Styles of [`Axis`] and [`Dataset`] will have priority over this style. #[must_use = "method moves the value of self and returns the modified value"] @@ -444,14 +457,16 @@ impl<'a> TimeChart<'a> { self } - /// Sets the constraints used to determine whether the legend should be shown or not. + /// Sets the constraints used to determine whether the legend should be + /// shown or not. /// - /// The tuple's first constraint is used for the width and the second for the height. If the - /// legend takes more space than what is allowed by any constraint, the legend is hidden. - /// [`Constraint::Min`] is an exception and will always show the legend. + /// The tuple's first constraint is used for the width and the second for + /// the height. If the legend takes more space than what is allowed by + /// any constraint, the legend is hidden. [`Constraint::Min`] is an + /// exception and will always show the legend. /// - /// If this is not set, the default behavior is to hide the legend if it is greater than 25% of - /// the chart, either horizontally or vertically. + /// If this is not set, the default behavior is to hide the legend if it is + /// greater than 25% of the chart, either horizontally or vertically. #[must_use = "method moves the value of self and returns the modified value"] pub fn hidden_legend_constraints( mut self, constraints: (Constraint, Constraint), @@ -464,8 +479,9 @@ impl<'a> TimeChart<'a> { /// /// The default is [`LegendPosition::TopRight`]. /// - /// If [`None`] is given, hide the legend even if [`hidden_legend_constraints`] determines it - /// should be shown. In contrast, if `Some(...)` is given, [`hidden_legend_constraints`] might + /// If [`None`] is given, hide the legend even if + /// [`hidden_legend_constraints`] determines it should be shown. In + /// contrast, if `Some(...)` is given, [`hidden_legend_constraints`] might /// still decide whether to show the legend or not. /// /// See [`LegendPosition`] for all available positions. @@ -477,8 +493,8 @@ impl<'a> TimeChart<'a> { self } - /// Compute the internal layout of the chart given the area. If the area is too small some - /// elements may be automatically hidden + /// Compute the internal layout of the chart given the area. If the area is + /// too small some elements may be automatically hidden fn layout(&self, area: Rect) -> ChartLayout { let mut layout = ChartLayout::default(); if area.height == 0 || area.width == 0 { @@ -593,7 +609,8 @@ impl<'a> TimeChart<'a> { }; max_width = max(max_width, width_left_of_y_axis); } - // labels of y axis and first label of x axis can take at most 1/3rd of the total width + // labels of y axis and first label of x axis can take at most 1/3rd of the + // total width max_width.min(area.width / 3) } @@ -703,9 +720,9 @@ impl Widget for TimeChart<'_> { return; } - // Sample the style of the entire widget. This sample will be used to reset the style of - // the cells that are part of the components put on top of the grah area (i.e legend and - // axis names). + // Sample the style of the entire widget. This sample will be used to reset the + // style of the cells that are part of the components put on top of the + // grah area (i.e legend and axis names). let original_style = buf.get(area.left(), area.top()).style(); let layout = self.layout(chart_area); @@ -1020,7 +1037,8 @@ mod tests { let layout = widget.layout(buffer.area); assert!(layout.legend_area.is_some()); - assert_eq!(layout.legend_area.unwrap().height, 4); // 2 for borders, 2 for rows + assert_eq!(layout.legend_area.unwrap().height, 4); // 2 for borders, 2 + // for rows } #[test] diff --git a/src/canvas/components/tui_widget/time_chart/canvas.rs b/src/canvas/components/tui_widget/time_chart/canvas.rs index be723bee..7f9b1c92 100644 --- a/src/canvas/components/tui_widget/time_chart/canvas.rs +++ b/src/canvas/components/tui_widget/time_chart/canvas.rs @@ -1,10 +1,12 @@ //! Vendored from <https://github.com/fdehau/tui-rs/blob/fafad6c96109610825aad89c4bba5253e01101ed/src/widgets/canvas/mod.rs> //! and <https://github.com/ratatui-org/ratatui/blob/c8dd87918d44fff6d4c3c78e1fc821a3275db1ae/src/widgets/canvas.rs>. //! -//! The main thing this is pulled in for is overriding how `BrailleGrid`'s draw logic works, as changing it is -//! needed in order to draw all datasets in only one layer back in [`super::TimeChart::render`]. More specifically, -//! the current implementation in ratatui `|=`s all the cells together if they overlap, but since we are smashing -//! all the layers together which may have different colours, we instead just _replace_ whatever was in that cell +//! The main thing this is pulled in for is overriding how `BrailleGrid`'s draw +//! logic works, as changing it is needed in order to draw all datasets in only +//! one layer back in [`super::TimeChart::render`]. More specifically, +//! the current implementation in ratatui `|=`s all the cells together if they +//! overlap, but since we are smashing all the layers together which may have +//! different colours, we instead just _replace_ whatever was in that cell //! with the newer colour + character. //! //! See <https://github.com/ClementTsang/bottom/pull/918> and <https://github.com/ClementTsang/bottom/pull/937> for the @@ -285,19 +287,21 @@ pub struct Painter<'a, 'b> { resolution: (f64, f64), } -/// The HalfBlockGrid is a grid made up of cells each containing a half block character. +/// The HalfBlockGrid is a grid made up of cells each containing a half block +/// character. /// -/// In terminals, each character is usually twice as tall as it is wide. Unicode has a couple of -/// vertical half block characters, the upper half block '▀' and lower half block '▄' which take up -/// half the height of a normal character but the full width. Together with an empty space ' ' and a -/// full block '█', we can effectively double the resolution of a single cell. In addition, because -/// each character can have a foreground and background color, we can control the color of the upper -/// and lower half of each cell. This allows us to draw shapes with a resolution of 1x2 "pixels" per -/// cell. +/// In terminals, each character is usually twice as tall as it is wide. Unicode +/// has a couple of vertical half block characters, the upper half block '▀' and +/// lower half block '▄' which take up half the height of a normal character but +/// the full width. Together with an empty space ' ' and a full block '█', we +/// can effectively double the resolution of a single cell. In addition, because +/// each character can have a foreground and background color, we can control +/// the color of the upper and lower half of each cell. This allows us to draw +/// shapes with a resolution of 1x2 "pixels" per cell. /// -/// This allows for more flexibility than the BrailleGrid which only supports a single -/// foreground color for each 2x4 dots cell, and the CharGrid which only supports a single -/// character for each cell. +/// This allows for more flexibility than the BrailleGrid which only supports a +/// single foreground color for each 2x4 dots cell, and the CharGrid which only +/// supports a single character for each cell. #[derive(Debug, Default, Clone, Eq, PartialEq, Hash)] struct HalfBlockGrid { /// width of the grid in number of terminal columns @@ -309,8 +313,8 @@ struct HalfBlockGrid { } impl HalfBlockGrid { - /// Create a new [`HalfBlockGrid`] with the given width and height measured in terminal columns - /// and rows respectively. + /// Create a new [`HalfBlockGrid`] with the given width and height measured + /// in terminal columns and rows respectively. fn new(width: u16, height: u16) -> HalfBlockGrid { HalfBlockGrid { width, @@ -334,10 +338,11 @@ impl Grid for HalfBlockGrid { } fn save(&self) -> Layer { - // Given that we store the pixels in a grid, and that we want to use 2 pixels arranged - // vertically to form a single terminal cell, which can be either empty, upper half block, - // lower half block or full block, we need examine the pixels in vertical pairs to decide - // what character to print in each cell. So these are the 4 states we use to represent each + // Given that we store the pixels in a grid, and that we want to use 2 pixels + // arranged vertically to form a single terminal cell, which can be + // either empty, upper half block, lower half block or full block, we + // need examine the pixels in vertical pairs to decide what character to + // print in each cell. So these are the 4 states we use to represent each // cell: // // 1. upper: reset, lower: reset => ' ' fg: reset / bg: reset @@ -345,20 +350,23 @@ impl Grid for HalfBlockGrid { // 3. upper: color, lower: reset => '▀' fg: upper color / bg: reset // 4. upper: color, lower: color => '▀' fg: upper color / bg: lower color // - // Note that because the foreground reset color (i.e. default foreground color) is usually - // not the same as the background reset color (i.e. default background color), we need to - // swap around the colors for that state (2 reset/color). + // Note that because the foreground reset color (i.e. default foreground color) + // is usually not the same as the background reset color (i.e. default + // background color), we need to swap around the colors for that state + // (2 reset/color). // - // When the upper and lower colors are the same, we could continue to use an upper half - // block, but we choose to use a full block instead. This allows us to write unit tests that - // treat the cell as a single character instead of two half block characters. + // When the upper and lower colors are the same, we could continue to use an + // upper half block, but we choose to use a full block instead. This + // allows us to write unit tests that treat the cell as a single + // character instead of two half block characters. - // Note we implement this slightly differently to what is done in ratatui's repo, - // since their version doesn't seem to compile for me... - // TODO: Whenever I add this as a valid marker, make sure this works fine with the overriden - // time_chart drawing-layer-thing. + // Note we implement this slightly differently to what is done in ratatui's + // repo, since their version doesn't seem to compile for me... + // TODO: Whenever I add this as a valid marker, make sure this works fine with + // the overriden time_chart drawing-layer-thing. - // Join the upper and lower rows, and emit a tuple vector of strings to print, and their colours. + // Join the upper and lower rows, and emit a tuple vector of strings to print, + // and their colours. let (string, colors) = self .pixels .iter() @@ -503,8 +511,8 @@ impl<'a> Context<'a> { } } -/// The Canvas widget may be used to draw more detailed figures using braille patterns (each -/// cell can have a braille character in 8 different positions). +/// The Canvas widget may be used to draw more detailed figures using braille +/// patterns (each cell can have a braille character in 8 different positions). pub struct Canvas<'a, F> where F: Fn(&mut Context<'_>), @@ -558,9 +566,10 @@ where self } - /// Change the type of points used to draw the shapes. By default the braille patterns are used - /// as they provide a more fine grained result but you might want to use the simple dot or - /// block instead if the targeted terminal does not support those symbols. + /// Change the type of points used to draw the shapes. By default the + /// braille patterns are used as they provide a more fine grained result + /// but you might want to use the simple dot or block instead if the + /// targeted terminal does not support those symbols. /// /// # Examples /// diff --git a/src/canvas/components/tui_widget/time_chart/points.rs b/src/canvas/components/tui_widget/time_chart/points.rs index 350f9da8..70283500 100644 --- a/src/canvas/components/tui_widget/time_chart/points.rs +++ b/src/canvas/components/tui_widget/time_chart/points.rs @@ -22,9 +22,10 @@ impl TimeChart<'_> { // See <https://github.com/ClementTsang/bottom/pull/918> and <https://github.com/ClementTsang/bottom/pull/937> // for the original motivation. // - // We also additionally do some interpolation logic because we may get caught missing some points - // when drawing, but we generally want to avoid jarring gaps between the edges when there's - // a point that is off screen and so a line isn't drawn (right edge generally won't have this issue + // We also additionally do some interpolation logic because we may get caught + // missing some points when drawing, but we generally want to avoid + // jarring gaps between the edges when there's a point that is off + // screen and so a line isn't drawn (right edge generally won't have this issue // issue but it can happen in some cases). for dataset in &self.datasets { @@ -112,7 +113,8 @@ impl TimeChart<'_> { } } -/// Returns the start index and potential interpolation index given the start time and the dataset. +/// Returns the start index and potential interpolation index given the start +/// time and the dataset. fn get_start(dataset: &Dataset<'_>, start_bound: f64) -> (usize, Option<usize>) { match dataset .data @@ -123,18 +125,20 @@ fn get_start(dataset: &Dataset<'_>, start_bound: f64) -> (usize, Option<usize>) } } -/// Returns the end position and potential interpolation index given the end time and the dataset. +/// Returns the end position and potential interpolation index given the end +/// time and the dataset. fn get_end(dataset: &Dataset<'_>, end_bound: f64) -> (usize, Option<usize>) { match dataset .data .binary_search_by(|(x, _y)| partial_ordering(x, &end_bound)) { - // In the success case, this means we found an index. Add one since we want to include this index and we - // expect to use the returned index as part of a (m..n) range. + // In the success case, this means we found an index. Add one since we want to include this + // index and we expect to use the returned index as part of a (m..n) range. Ok(index) => (index.saturating_add(1), None), - // In the fail case, this means we did not find an index, and the returned index is where one would *insert* - // the location. This index is where one would insert to fit inside the dataset - and since this is an end - // bound, index is, in a sense, already "+1" for our range later. + // In the fail case, this means we did not find an index, and the returned index is where + // one would *insert* the location. This index is where one would insert to fit + // inside the dataset - and since this is an end bound, index is, in a sense, + // already "+1" for our range later. Err(index) => (index, { let sum = index.checked_add(1); match sum { @@ -145,7 +149,8 @@ fn get_end(dataset: &Dataset<'_>, end_bound: f64) -> (usize, Option<usize>) { } } -/// Returns the y-axis value for a given `x`, given two points to draw a line between. +/// Returns the y-axis value for a given `x`, given two points to draw a line +/// between. fn interpolate_point(older_point: &Point, newer_point: &Point, x: f64) -> f64 { let delta_x = newer_point.0 - older_point.0; let delta_y = newer_point.1 - older_point.1; diff --git a/src/canvas/components/widget_carousel.rs b/src/canvas/components/widget_carousel.rs index 9f7baba7..fd5b7a4c 100644 --- a/src/canvas/components/widget_carousel.rs +++ b/src/canvas/components/widget_carousel.rs @@ -129,15 +129,18 @@ impl Painter { if app_state.should_get_widget_bounds() { // Some explanations for future readers: - // - The "height" as of writing of this entire widget is 2. If it's 1, it occasionally doesn't draw. + // - The "height" as of writing of this entire widget is 2. If it's 1, it + // occasionally doesn't draw. // - As such, the buttons are only on the lower part of this 2-high widget. - // - So, we want to only check at one location, the `draw_loc.y + 1`, and that's it. + // - So, we want to only check at one location, the `draw_loc.y + 1`, and that's + // it. // - But why is it "+2" then? Well, it's because I have a REALLY ugly hack // for mouse button checking, since most button checks are of the form `(draw_loc.y + draw_loc.height)`, // and the same for the x and width. Unfortunately, if you check using >= and <=, the outer bound is // actually too large - so, we assume all of them are one too big and check via < (see // https://github.com/ClementTsang/bottom/pull/459 for details). - // - So in other words, to make it simple, we keep this to a standard and overshoot by one here. + // - So in other words, to make it simple, we keep this to a standard and + // overshoot by one here. if let Some(basic_table) = &mut app_state.states.basic_table_widget_state { basic_table.left_tlc = Some((margined_draw_loc[0].x, margined_draw_loc[0].y + 1)); diff --git a/src/canvas/dialogs/dd_dialog.rs b/src/canvas/dialogs/dd_dialog.rs index 876552bf..ff41df4a 100644 --- a/src/canvas/dialogs/dd_dialog.rs +++ b/src/canvas/dialogs/dd_dialog.rs @@ -249,14 +249,15 @@ impl Painter { const SIGNAL: usize = if cfg!(target_os = "windows") { 1 } else { 15 }; // This is kinda weird, but the gist is: - // - We have three sections; we put our mouse bounding box for the "yes" button at the very right edge - // of the left section and 3 characters back. We then give it a buffer size of 1 on the x-coordinate. - // - Same for the "no" button, except it is the right section and we do it from the start of the right - // section. + // - We have three sections; we put our mouse bounding box for the "yes" button + // at the very right edge of the left section and 3 characters back. We then + // give it a buffer size of 1 on the x-coordinate. + // - Same for the "no" button, except it is the right section and we do it from + // the start of the right section. // - // Lastly, note that mouse detection for the dd buttons assume correct widths. As such, we correct - // them here and check with >= and <= mouse bound checks, as opposed to how we do it elsewhere with - // >= and <. See https://github.com/ClementTsang/bottom/pull/459 for details. + // Lastly, note that mouse detection for the dd buttons assume correct widths. + // As such, we correct them here and check with >= and <= mouse + // bound checks, as opposed to how we do it elsewhere with >= and <. See https://github.com/ClementTsang/bottom/pull/459 for details. app_state.delete_dialog_state.button_positions = vec![ // Yes ( diff --git a/src/canvas/dialogs/help_dialog.rs b/src/canvas/dialogs/help_dialog.rs index 9dafeb2e..eac3fbc4 100644 --- a/src/canvas/dialogs/help_dialog.rs +++ b/src/canvas/dialogs/help_dialog.rs @@ -63,8 +63,8 @@ impl Painter { .border_style(self.colours.border_style); if app_state.should_get_widget_bounds() { - // We must also recalculate how many lines are wrapping to properly get scrolling to work on - // small terminal sizes... oh joy. + // We must also recalculate how many lines are wrapping to properly get + // scrolling to work on small terminal sizes... oh joy. app_state.help_dialog_state.height = block.inner(draw_loc).height; diff --git a/src/canvas/styling.rs b/src/canvas/styling.rs index 22a88a71..53ff120b 100644 --- a/src/canvas/styling.rs +++ b/src/canvas/styling.rs @@ -6,7 +6,7 @@ use tui::style::{Color, Style}; use super::ColourScheme; pub use crate::options::ConfigV1; -use crate::{constants::*, options::colours::ConfigColours, utils::error}; +use crate::{constants::*, options::colours::ColoursConfig, utils::error}; pub struct CanvasStyling { pub currently_selected_text_colour: Color, @@ -154,7 +154,7 @@ impl CanvasStyling { Ok(canvas_colours) } - pub fn set_colours_from_palette(&mut self, colours: &ConfigColours) -> anyhow::Result<()> { + pub fn set_colours_from_palette(&mut self, colours: &ColoursConfig) -> anyhow::Result<()> { // CPU try_set_colour!(self.avg_colour_style, colours, avg_cpu_color); try_set_colour!(self.all_colour_style, colours, all_cpu_color); diff --git a/src/canvas/widgets/battery_display.rs b/src/canvas/widgets/battery_display.rs index c2cc91bc..6fa95ce0 100644 --- a/src/canvas/widgets/battery_display.rs +++ b/src/canvas/widgets/battery_display.rs @@ -111,8 +111,9 @@ impl Painter { tab_click_locs .push(((current_x, current_y), (current_x + width, current_y))); - // +4 because we want to go one space, then one space past to get to the '|', then 2 more - // to start at the blank space before the tab label. + // +4 because we want to go one space, then one space past to get to the + // '|', then 2 more to start at the blank space + // before the tab label. current_x += width + 4; } battery_widget_state.tab_click_locs = Some(tab_click_locs); diff --git a/src/canvas/widgets/cpu_basic.rs b/src/canvas/widgets/cpu_basic.rs index d0fd539a..81ccad78 100644 --- a/src/canvas/widgets/cpu_basic.rs +++ b/src/canvas/widgets/cpu_basic.rs @@ -33,8 +33,8 @@ impl Painter { // many rows and columns we have in draw_loc (-2 on both sides for border?). // I think what we can do is try to fit in as many in one column as possible. // If not, then add a new column. - // Then, from this, split the row space across ALL columns. From there, generate - // the desired lengths. + // Then, from this, split the row space across ALL columns. From there, + // generate the desired lengths. if app_state.current_widget.widget_id == widget_id { f.render_widget( diff --git a/src/canvas/widgets/cpu_graph.rs b/src/canvas/widgets/cpu_graph.rs index 85200e98..7d7377f7 100644 --- a/src/canvas/widgets/cpu_graph.rs +++ b/src/canvas/widgets/cpu_graph.rs @@ -251,7 +251,8 @@ impl Painter { .widget_states .get_mut(&(widget_id - 1)) { - // TODO: This line (and the one above, see caller) is pretty dumb but I guess needed for now. Refactor if possible! + // TODO: This line (and the one above, see caller) is pretty dumb but I guess + // needed for now. Refactor if possible! cpu_widget_state.is_legend_hidden = false; let is_on_widget = widget_id == app_state.current_widget.widget_id; diff --git a/src/canvas/widgets/network_graph.rs b/src/canvas/widgets/network_graph.rs index ec7e5fbd..227c18ba 100644 --- a/src/canvas/widgets/network_graph.rs +++ b/src/canvas/widgets/network_graph.rs @@ -42,8 +42,8 @@ impl Painter { 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. + // 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 = @@ -74,7 +74,8 @@ impl Painter { // 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!) + // - 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( @@ -216,7 +217,8 @@ fn get_max_entry( rx: &[Point], tx: &[Point], time_start: f64, network_scale_type: &AxisScaling, network_use_binary_prefix: bool, ) -> Point { - /// Determines a "fake" max value in circumstances where we couldn't find one from the data. + /// 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 { @@ -238,8 +240,9 @@ fn get_max_entry( } } - // 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... + // 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), @@ -337,26 +340,31 @@ fn adjust_network_data_point( network_use_binary_prefix: bool, ) -> (f64, Vec<String>) { // 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. + // - 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 + // 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 ratatui? Well, if we are using intervals that tie in perfectly to the max - // value we want... then it's actually not that hard. Since ratatui accepts a vector as labels and will - // properly space them all out... we just work with that and space it out properly. + // So, how do we do this in ratatui? Well, if we are using intervals that tie + // in perfectly to the max value we want... then it's actually not that + // hard. Since ratatui 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. + // 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! + // 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", @@ -411,8 +419,9 @@ fn adjust_network_data_point( ) }; - // 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. + // 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<String> = vec![ @@ -422,7 +431,8 @@ fn adjust_network_data_point( format!("{:.1}", base_unit * 1.5), ] .into_iter() - .map(|s| format!("{s:>5}")) // Pull 5 as the longest legend value is generally going to be 5 digits (if they somehow hit over 5 terabits per second) + .map(|s| format!("{s:>5}")) // 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) diff --git a/src/canvas/widgets/process_table.rs b/src/canvas/widgets/process_table.rs index 0bf698d6..11fe1b1e 100644 --- a/src/canvas/widgets/process_table.rs +++ b/src/canvas/widgets/process_table.rs @@ -20,7 +20,8 @@ const SORT_MENU_WIDTH: u16 = 7; impl Painter { /// Draws and handles all process-related drawing. Use this. - /// - `widget_id` here represents the widget ID of the process widget itself! + /// - `widget_id` here represents the widget ID of the process widget + /// itself! pub fn draw_process( &self, f: &mut Frame<'_>, app_state: &mut App, draw_loc: Rect, draw_border: bool, widget_id: u64, @@ -106,7 +107,8 @@ impl Painter { } /// Draws the process search field. - /// - `widget_id` represents the widget ID of the search box itself --- NOT the process widget + /// - `widget_id` represents the widget ID of the search box itself --- NOT + /// the process widget /// state that is stored. fn draw_search_field( &self, f: &mut Frame<'_>, app_state: &mut App, draw_loc: Rect, draw_border: bool, @@ -310,7 +312,8 @@ impl Painter { } /// Draws the process sort box. - /// - `widget_id` represents the widget ID of the sort box itself --- NOT the process widget + /// - `widget_id` represents the widget ID of the sort box itself --- NOT + /// the process widget /// state that is stored. fn draw_sort_table( &self, f: &mut Frame<'_>, app_state: &mut App, draw_loc: Rect, widget_id: u64, diff --git a/src/constants.rs b/src/constants.rs index e3750a4a..797de3f9 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,6 +1,6 @@ use tui::widgets::Borders; -use crate::options::ConfigColours; +use crate::options::ColoursConfig; // Default widget ID pub const DEFAULT_WIDGET_ID: u64 = 56709; @@ -16,7 +16,8 @@ pub const TICK_RATE_IN_MILLISECONDS: u64 = 200; pub const DEFAULT_REFRESH_RATE_IN_MILLISECONDS: u64 = 1000; pub const MAX_KEY_TIMEOUT_IN_MILLISECONDS: u64 = 1000; -// Limits for when we should stop showing table gaps/labels (anything less means not shown) +// Limits for when we should stop showing table gaps/labels (anything less means +// not shown) pub const TABLE_GAP_HEIGHT_LIMIT: u16 = 7; pub const TIME_LABEL_HEIGHT_LIMIT: u16 = 7; @@ -25,8 +26,8 @@ pub const SIDE_BORDERS: Borders = Borders::LEFT.union(Borders::RIGHT); // Colour profiles // TODO: Generate these with a macro or something... -pub fn default_light_mode_colour_palette() -> ConfigColours { - ConfigColours { +pub fn default_light_mode_colour_palette() -> ColoursConfig { + ColoursConfig { text_color: Some("black".into()), border_color: Some("black".into()), table_header_color: Some("black".into()), @@ -61,12 +62,12 @@ pub fn default_light_mode_colour_palette() -> ConfigColours { "Blue".into(), "Red".into(), ]), - ..ConfigColours::default() + ..ColoursConfig::default() } } -pub fn gruvbox_colour_palette() -> ConfigColours { - ConfigColours { +pub fn gruvbox_colour_palette() -> ColoursConfig { + ColoursConfig { table_header_color: Some("#83a598".into()), all_cpu_color: Some("#8ec07c".into()), avg_cpu_color: Some("#fb4934".into()), @@ -124,8 +125,8 @@ pub fn gruvbox_colour_palette() -> ConfigColours { } } -pub fn gruvbox_light_colour_palette() -> ConfigColours { - ConfigColours { +pub fn gruvbox_light_colour_palette() -> ColoursConfig { + ColoursConfig { table_header_color: Some("#076678".into()), all_cpu_color: Some("#8ec07c".into()), avg_cpu_color: Some("#fb4934".into()), @@ -183,8 +184,8 @@ pub fn gruvbox_light_colour_palette() -> ConfigColours { } } -pub fn nord_colour_palette() -> ConfigColours { - ConfigColours { +pub fn nord_colour_palette() -> ColoursConfig { + ColoursConfig { table_header_color: Some("#81a1c1".into()), all_cpu_color: Some("#88c0d0".into()), avg_cpu_color: Some("#8fbcbb".into()), @@ -230,8 +231,8 @@ pub fn nord_colour_palette() -> ConfigColours { } } -pub fn nord_light_colour_palette() -> ConfigColours { - ConfigColours { +pub fn nord_light_colour_palette() -> ColoursConfig { + ColoursConfig { table_header_color: Some("#5e81ac".into()), all_cpu_color: Some("#81a1c1".into()), avg_cpu_color: Some("#8fbcbb".into()), @@ -601,16 +602,51 @@ pub const CONFIG_TEXT: &str = r#"# This is a default config file for bottom. Al # Where to place the legend for the network widget. One of "none", "top-left", "top", "top-right", "left", "right", "bottom-left", "bottom", "bottom-right". #network_legend = "TopRight". -# These are flags around the process widget. +# Processes widget configuration #[processes] # The columns shown by the process widget. The following columns are supported: -# PID, Name, CPU%, Mem%, R/s, W/s, T.Read, T.Write, User, State, Time, GMem%, GPU% +# PID, Name, CPU%, Mem%, R/s, W/s, T.Read, T.Write, User, State, Time, GMem%, GPU% #columns = ["PID", "Name", "CPU%", "Mem%", "R/s", "W/s", "T.Read", "T.Write", "User", "State", "GMEM%", "GPU%"] -# [cpu] +# CPU widget configuration +#[cpu] # One of "all" (default), "average"/"avg" # default = "average" +# Disk widget configuration +#[disk] +#[disk.name_filter] +#is_list_ignored = true +#list = ["/dev/sda\\d+", "/dev/nvme0n1p2"] +#regex = true +#case_sensitive = false +#whole_word = false + +#[disk.mount_filter] +#is_list_ignored = true +#list = ["/mnt/.*", "/boot"] +#regex = true +#case_sensitive = false +#whole_word = false + +# Temperature widget configuration +#[temperature] +#[temperature.sensor_filter] +#is_list_ignored = true +#list = ["cpu", "wifi"] +#regex = false +#case_sensitive = false +#whole_word = false + +# Network widget configuration +#[network] +#[network.interface_filter] +#is_list_ignored = true +#list = ["virbr0.*"] +#regex = true +#case_sensitive = false +#whole_word = false + # These are all the components that support custom theming. Note that colour support # will depend on terminal support. #[colors] # Uncomment if you want to use custom colors @@ -681,37 +717,6 @@ pub const CONFIG_TEXT: &str = r#"# This is a default config file for bottom. Al # [[row.child]] # type="proc" # default=true - - -# Filters - you can hide specific temperature sensors, network interfaces, and disks using filters. This is admittedly -# a bit hard to use as of now, and there is a planned in-app interface for managing this in the future: -#[disk_filter] -#is_list_ignored = true -#list = ["/dev/sda\\d+", "/dev/nvme0n1p2"] -#regex = true -#case_sensitive = false -#whole_word = false - -#[mount_filter] -#is_list_ignored = true -#list = ["/mnt/.*", "/boot"] -#regex = true -#case_sensitive = false -#whole_word = false - -#[temp_filter] -#is_list_ignored = true -#list = ["cpu", "wifi"] -#regex = false -#case_sensitive = false -#whole_word = false - -#[net_filter] -#is_list_ignored = true -#list = ["virbr0.*"] -#regex = true -#case_sensitive = false -#whole_word = false "#; pub const CONFIG_TOP_HEAD: &str = r##"# This is bottom's config file. @@ -773,8 +778,8 @@ mod test { } } - /// This test exists because previously, [`SIDE_BORDERS`] was set incorrectly after I moved from - /// tui-rs to ratatui. + /// This test exists because previously, [`SIDE_BORDERS`] was set + /// incorrectly after I moved from tui-rs to ratatui. #[test] fn assert_side_border_bits_match() { assert_eq!( diff --git a/src/data_collection.rs b/src/data_collection.rs index 3095405a..45c8fe0a 100644 --- a/src/data_collection.rs +++ b/src/data_collection.rs @@ -98,7 +98,8 @@ impl Data { } } -/// A wrapper around the sysinfo data source. We use sysinfo for the following data: +/// A wrapper around the sysinfo data source. We use sysinfo for the following +/// data: /// - CPU usage /// - Memory usage /// - Network usage @@ -183,7 +184,7 @@ impl DataCollector { temperature_type: TemperatureType::Celsius, use_current_cpu_total: false, unnormalized_cpu: false, - last_collection_time: Instant::now() - Duration::from_secs(600), // Initialize it to the past to force it to load on initialization. + last_collection_time: Instant::now() - Duration::from_secs(600), /* Initialize it to the past to force it to load on initialization. */ total_rx: 0, total_tx: 0, show_average_cpu: false, @@ -255,7 +256,8 @@ impl DataCollector { /// - Disk (Windows) /// - Temperatures (non-Linux) fn refresh_sysinfo_data(&mut self) { - // Refresh the list of objects once every minute. If it's too frequent it can cause segfaults. + // Refresh the list of objects once every minute. If it's too frequent it can + // cause segfaults. const LIST_REFRESH_TIME: Duration = Duration::from_secs(60); let refresh_start = Instant::now(); @@ -374,9 +376,9 @@ impl DataCollector { fn update_processes(&mut self) { if self.widgets_to_harvest.use_proc { if let Ok(mut process_list) = self.get_processes() { - // NB: To avoid duplicate sorts on rerenders/events, we sort the processes by PID here. - // We also want to avoid re-sorting *again* later on if we're sorting by PID, since we already - // did it here! + // NB: To avoid duplicate sorts on rerenders/events, we sort the processes by + // PID here. We also want to avoid re-sorting *again* later on + // if we're sorting by PID, since we already did it here! process_list.sort_unstable_by_key(|p| p.pid); self.data.list_of_processes = Some(process_list); } @@ -473,12 +475,15 @@ impl DataCollector { } } -/// We set a sleep duration between 10ms and 250ms, ideally sysinfo's [`sysinfo::MINIMUM_CPU_UPDATE_INTERVAL`] + 1. +/// We set a sleep duration between 10ms and 250ms, ideally sysinfo's +/// [`sysinfo::MINIMUM_CPU_UPDATE_INTERVAL`] + 1. /// -/// We bound the upper end to avoid waiting too long (e.g. FreeBSD is 1s, which I'm fine with losing -/// accuracy on for the first refresh), and we bound the lower end just to avoid the off-chance that -/// refreshing too quickly causes problems. This second case should only happen on unsupported -/// systems via sysinfo, in which case [`sysinfo::MINIMUM_CPU_UPDATE_INTERVAL`] is defined as 0. +/// We bound the upper end to avoid waiting too long (e.g. FreeBSD is 1s, which +/// I'm fine with losing accuracy on for the first refresh), and we bound the +/// lower end just to avoid the off-chance that refreshing too quickly causes +/// problems. This second case should only happen on unsupported systems via +/// sysinfo, in which case [`sysinfo::MINIMUM_CPU_UPDATE_INTERVAL`] is defined +/// as 0. /// /// We also do `INTERVAL + 1` for some wiggle room, just in case. const fn get_sleep_duration() -> Duration { diff --git a/src/data_collection/batteries.rs b/src/data_collection/batteries.rs index 8c0e4a92..a155ad2d 100644 --- a/src/data_collection/batteries.rs +++ b/src/data_collection/batteries.rs @@ -1,6 +1,7 @@ //! Data collection for batteries. //! -//! For Linux, macOS, Windows, FreeBSD, Dragonfly, and iOS, this is handled by the battery crate. +//! For Linux, macOS, Windows, FreeBSD, Dragonfly, and iOS, this is handled by +//! the battery crate. cfg_if::cfg_if! { if #[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux", target_os = "freebsd", target_os = "dragonfly", target_os = "ios"))] { diff --git a/src/data_collection/cpu/sysinfo.rs b/src/data_collection/cpu/sysinfo.rs index 196e8a87..707822f9 100644 --- a/src/data_collection/cpu/sysinfo.rs +++ b/src/data_collection/cpu/sysinfo.rs @@ -32,7 +32,8 @@ pub fn get_cpu_data_list(sys: &System, show_average_cpu: bool) -> crate::error:: } pub fn get_load_avg() -> crate::error::Result<LoadAvgHarvest> { - // The API for sysinfo apparently wants you to call it like this, rather than using a &System. + // The API for sysinfo apparently wants you to call it like this, rather than + // using a &System. let LoadAvg { one, five, fifteen } = sysinfo::System::load_average(); Ok([one as f32, five as f32, fifteen as f32]) diff --git a/src/data_collection/disks.rs b/src/data_collection/disks.rs index 8d55de98..9aba0579 100644 --- a/src/data_collection/disks.rs +++ b/src/data_collection/disks.rs @@ -86,16 +86,18 @@ cfg_if! { } } -/// Whether to keep the current disk entry given the filters, disk name, and disk mount. -/// Precedence ordering in the case where name and mount filters disagree, "allow" -/// takes precedence over "deny". +/// Whether to keep the current disk entry given the filters, disk name, and +/// disk mount. Precedence ordering in the case where name and mount filters +/// disagree, "allow" takes precedence over "deny". /// /// For implementation, we do this as follows: /// -/// 1. Is the entry allowed through any filter? That is, does it match an entry in a -/// filter where `is_list_ignored` is `false`? If so, we always keep this entry. -/// 2. Is the entry denied through any filter? That is, does it match an entry in a -/// filter where `is_list_ignored` is `true`? If so, we always deny this entry. +/// 1. Is the entry allowed through any filter? That is, does it match an entry +/// in a filter where `is_list_ignored` is `false`? If so, we always keep +/// this entry. +/// 2. Is the entry denied through any filter? That is, does it match an entry +/// in a filter where `is_list_ignored` is `true`? If so, we always deny this +/// entry. /// 3. Anything else is allowed. pub fn keep_disk_entry( disk_name: &str, mount_point: &str, disk_filter: &Option<Filter>, mount_filter: &Option<Filter>, diff --git a/src/data_collection/disks/freebsd.rs b/src/data_collection/disks/freebsd.rs index 2d16e1c5..095c5b12 100644 --- a/src/data_collection/disks/freebsd.rs +++ b/src/data_collection/disks/freebsd.rs @@ -28,7 +28,8 @@ struct FileSystem { } pub fn get_io_usage() -> error::Result<IoHarvest> { - // TODO: Should this (and other I/O collectors) fail fast? In general, should collection ever fail fast? + // TODO: Should this (and other I/O collectors) fail fast? In general, should + // collection ever fail fast? #[allow(unused_mut)] let mut io_harvest: HashMap<String, Option<IoData>> = get_disk_info().map(|storage_system_information| { diff --git a/src/data_collection/disks/unix.rs b/src/data_collection/disks/unix.rs index 309a0a19..3178f94c 100644 --- a/src/data_collection/disks/unix.rs +++ b/src/data_collection/disks/unix.rs @@ -1,5 +1,5 @@ -//! Disk stats for Unix-like systems that aren't supported through other means. Officially, -//! for now, this means Linux and macOS. +//! Disk stats for Unix-like systems that aren't supported through other means. +//! Officially, for now, this means Linux and macOS. mod file_systems; @@ -37,16 +37,21 @@ pub fn get_disk_usage(collector: &DataCollector) -> anyhow::Result<Vec<DiskHarve let name = partition.get_device_name(); let mount_point = partition.mount_point().to_string_lossy().to_string(); - // Precedence ordering in the case where name and mount filters disagree, "allow" takes precedence over "deny". + // Precedence ordering in the case where name and mount filters disagree, + // "allow" takes precedence over "deny". // // For implementation, we do this as follows: - // 1. Is the entry allowed through any filter? That is, does it match an entry in a filter where `is_list_ignored` is `false`? If so, we always keep this entry. - // 2. Is the entry denied through any filter? That is, does it match an entry in a filter where `is_list_ignored` is `true`? If so, we always deny this entry. + // 1. Is the entry allowed through any filter? That is, does it match an entry + // in a filter where `is_list_ignored` is `false`? If so, we always keep this + // entry. + // 2. Is the entry denied through any filter? That is, does it match an entry in + // a filter where `is_list_ignored` is `true`? If so, we always deny this + // entry. // 3. Anything else is allowed. if keep_disk_entry(&name, &mount_point, disk_filter, mount_filter) { - // The usage line can fail in some cases (for example, if you use Void Linux + LUKS, - // see https://github.com/ClementTsang/bottom/issues/419 for details). + // The usage line can fail in some cases (for example, if you use Void Linux + + // LUKS, see https://github.com/ClementTsang/bottom/issues/419 for details). if let Ok(usage) = partition.usage() { let total = usage.total(); diff --git a/src/data_collection/disks/unix/file_systems.rs b/src/data_collection/disks/unix/file_systems.rs index 713754e2..39ac0c51 100644 --- a/src/data_collection/disks/unix/file_systems.rs +++ b/src/data_collection/disks/unix/file_systems.rs @@ -5,8 +5,8 @@ use crate::multi_eq_ignore_ascii_case; /// Known filesystems. Original list from /// [heim](https://github.com/heim-rs/heim/blob/master/heim-disk/src/filesystem.rs). /// -/// All physical filesystems should have their own enum element and all virtual filesystems will go into -/// the [`FileSystem::Other`] element. +/// All physical filesystems should have their own enum element and all virtual +/// filesystems will go into the [`FileSystem::Other`] element. #[derive(Debug, Eq, PartialEq, Hash, Clone)] #[non_exhaustive] pub enum FileSystem { @@ -81,7 +81,8 @@ impl FileSystem { !self.is_virtual() } - /// Checks if filesystem is used for a virtual devices (such as `tmpfs` or `smb` mounts). + /// Checks if filesystem is used for a virtual devices (such as `tmpfs` or + /// `smb` mounts). #[inline] pub fn is_virtual(&self) -> bool { matches!(self, FileSystem::Other(..)) diff --git a/src/data_collection/disks/unix/linux/counters.rs b/src/data_collection/disks/unix/linux/counters.rs index bc2cb076..d0da4c82 100644 --- a/src/data_collection/disks/unix/linux/counters.rs +++ b/src/data_collection/disks/unix/linux/counters.rs @@ -28,7 +28,8 @@ impl FromStr for IoCounters { /// Converts a `&str` to an [`IoCounters`]. /// - /// Follows the format used in Linux 2.6+. Note that this completely ignores the following stats: + /// Follows the format used in Linux 2.6+. Note that this completely ignores + /// the following stats: /// - Discard stats from 4.18+ /// - Flush stats from 5.5+ /// @@ -71,7 +72,8 @@ pub fn io_stats() -> anyhow::Result<Vec<IoCounters>> { let mut reader = BufReader::new(File::open(PROC_DISKSTATS)?); let mut line = String::new(); - // This saves us from doing a string allocation on each iteration compared to `lines()`. + // This saves us from doing a string allocation on each iteration compared to + // `lines()`. while let Ok(bytes) = reader.read_line(&mut line) { if bytes > 0 { if let Ok(counters) = IoCounters::from_str(&line) { diff --git a/src/data_collection/disks/unix/linux/partition.rs b/src/data_collection/disks/unix/linux/partition.rs index 488ad8d9..5da832a0 100644 --- a/src/data_collection/disks/unix/linux/partition.rs +++ b/src/data_collection/disks/unix/linux/partition.rs @@ -43,8 +43,8 @@ impl Partition { /// Returns the device name for the partition. pub fn get_device_name(&self) -> String { if let Some(device) = self.device() { - // See if this disk is actually mounted elsewhere on Linux. This is a workaround properly map I/O - // in some cases (i.e. disk encryption, https://github.com/ClementTsang/bottom/issues/419). + // See if this disk is actually mounted elsewhere on Linux. This is a workaround + // properly map I/O in some cases (i.e. disk encryption, https://github.com/ClementTsang/bottom/issues/419). if let Ok(path) = std::fs::read_link(device) { if path.is_absolute() { path.into_os_string() @@ -87,7 +87,8 @@ impl Partition { let mut vfs = mem::MaybeUninit::<libc::statvfs>::uninit(); - // SAFETY: libc call, `path` is a valid C string and buf is a valid pointer to write to. + // SAFETY: libc call, `path` is a valid C string and buf is a valid pointer to + // write to. let result = unsafe { libc::statvfs(path.as_ptr(), vfs.as_mut_ptr()) }; if result == 0 { @@ -146,7 +147,8 @@ pub(crate) fn partitions() -> anyhow::Result<Vec<Partition>> { let mut reader = BufReader::new(File::open(PROC_MOUNTS)?); let mut line = String::new(); - // This saves us from doing a string allocation on each iteration compared to `lines()`. + // This saves us from doing a string allocation on each iteration compared to + // `lines()`. while let Ok(bytes) = reader.read_line(&mut line) { if bytes > 0 { if let Ok(partition) = Partition::from_str(&line) { @@ -171,7 +173,8 @@ pub(crate) fn physical_partitions() -> anyhow::Result<Vec<Partition>> { let mut reader = BufReader::new(File::open(PROC_MOUNTS)?); let mut line = String::new(); - // This saves us from doing a string allocation on each iteration compared to `lines()`. + // This saves us from doing a string allocation on each iteration compared to + // `lines()`. while let Ok(bytes) = reader.read_line(&mut line) { if bytes > 0 { if let Ok(partition) = Partition::from_str(&line) { diff --git a/src/data_collection/disks/unix/macos/counters.rs b/src/data_collection/disks/unix/macos/counters.rs index 1f03b297..7d3d6768 100644 --- a/src/data_collection/disks/unix/macos/counters.rs +++ b/src/data_collection/disks/unix/macos/counters.rs @@ -10,23 +10,24 @@ fn get_device_io(device: io_kit::IoObject) -> anyhow::Result<IoCounters> { // // Okay, so this is weird. // - // The problem is that if I have this check - this is what sources like psutil use, for - // example (see https://github.com/giampaolo/psutil/blob/7eadee31db2f038763a3a6f978db1ea76bbc4674/psutil/_psutil_osx.c#LL1422C20-L1422C20) + // The problem is that if I have this check - this is what sources like psutil + // use, for example (see https://github.com/giampaolo/psutil/blob/7eadee31db2f038763a3a6f978db1ea76bbc4674/psutil/_psutil_osx.c#LL1422C20-L1422C20) // then this will only return stuff like disk0. // - // The problem with this is that there is *never* a disk0 *disk* entry to correspond to this, - // so there will be entries like disk1 or whatnot. Someone's done some digging on the gopsutil - // repo (https://github.com/shirou/gopsutil/issues/855#issuecomment-610016435), and it seems + // The problem with this is that there is *never* a disk0 *disk* entry to + // correspond to this, so there will be entries like disk1 or whatnot. + // Someone's done some digging on the gopsutil repo (https://github.com/shirou/gopsutil/issues/855#issuecomment-610016435), and it seems // like this is a consequence of how Apple does logical volumes. // - // So with all that said, what I've found is that I *can* still get a mapping - but I have - // to disable the conform check, which... is weird. I'm not sure if this is valid at all. But - // it *does* seem to match Activity Monitor with regards to disk activity, so... I guess we - // can leave this for now...? + // So with all that said, what I've found is that I *can* still get a mapping - + // but I have to disable the conform check, which... is weird. I'm not sure + // if this is valid at all. But it *does* seem to match Activity Monitor + // with regards to disk activity, so... I guess we can leave this for + // now...? // if !parent.conforms_to_block_storage_driver() { - // anyhow::bail!("{parent:?}, the parent of {device:?} does not conform to IOBlockStorageDriver") - // } + // anyhow::bail!("{parent:?}, the parent of {device:?} does not conform to + // IOBlockStorageDriver") } let disk_props = device.properties()?; let parent_props = parent.properties()?; diff --git a/src/data_collection/disks/unix/macos/io_kit/bindings.rs b/src/data_collection/disks/unix/macos/io_kit/bindings.rs index 36576a60..93aaf9b8 100644 --- a/src/data_collection/disks/unix/macos/io_kit/bindings.rs +++ b/src/data_collection/disks/unix/macos/io_kit/bindings.rs @@ -49,7 +49,8 @@ extern "C" { entry: io_registry_entry_t, plane: *const libc::c_char, parent: *mut io_registry_entry_t, ) -> kern_return_t; - // pub fn IOObjectConformsTo(object: io_object_t, className: *const libc::c_char) -> mach2::boolean::boolean_t; + // pub fn IOObjectConformsTo(object: io_object_t, className: *const + // libc::c_char) -> mach2::boolean::boolean_t; pub fn IORegistryEntryCreateCFProperties( entry: io_registry_entry_t, properties: *mut CFMutableDictionaryRef, diff --git a/src/data_collection/disks/unix/macos/io_kit/io_iterator.rs b/src/data_collection/disks/unix/macos/io_kit/io_iterator.rs index 1f440466..b1b1f6a7 100644 --- a/src/data_collection/disks/unix/macos/io_kit/io_iterator.rs +++ b/src/data_collection/disks/unix/macos/io_kit/io_iterator.rs @@ -37,7 +37,8 @@ impl Iterator for IoIterator { fn next(&mut self) -> Option<Self::Item> { // Basically, we just stop when we hit 0. - // SAFETY: IOKit call, the passed argument (an `io_iterator_t`) is what is expected. + // SAFETY: IOKit call, the passed argument (an `io_iterator_t`) is what is + // expected. match unsafe { IOIteratorNext(self.0) } { 0 => None, io_object => Some(IoObject::from(io_object)), @@ -47,7 +48,8 @@ impl Iterator for IoIterator { impl Drop for IoIterator { fn drop(&mut self) { - // SAFETY: IOKit call, the passed argument (an `io_iterator_t`) is what is expected. + // SAFETY: IOKit call, the passed argument (an `io_iterator_t`) is what is + // expected. let result = unsafe { IOObjectRelease(self.0) }; assert_eq!(result, kern_return::KERN_SUCCESS); } diff --git a/src/data_collection/disks/unix/macos/io_kit/io_object.rs b/src/data_collection/disks/unix/macos/io_kit/io_object.rs index af110f0c..f7aa43ab 100644 --- a/src/data_collection/disks/unix/macos/io_kit/io_object.rs +++ b/src/data_collection/disks/unix/macos/io_kit/io_object.rs @@ -24,8 +24,9 @@ pub struct IoObject(io_object_t); impl IoObject { /// Returns a typed dictionary with this object's properties. pub fn properties(&self) -> anyhow::Result<CFDictionary<CFString, CFType>> { - // SAFETY: The IOKit call should be fine, the arguments are safe. The `assume_init` should also be fine, as - // we guard against it with a check against `result` to ensure it succeeded. + // SAFETY: The IOKit call should be fine, the arguments are safe. The + // `assume_init` should also be fine, as we guard against it with a + // check against `result` to ensure it succeeded. unsafe { let mut props = mem::MaybeUninit::<CFMutableDictionaryRef>::uninit(); @@ -45,8 +46,8 @@ impl IoObject { } } - /// Gets the [`kIOServicePlane`] parent [`io_object_t`] for this [`io_object_t`], if there - /// is one. + /// Gets the [`kIOServicePlane`] parent [`io_object_t`] for this + /// [`io_object_t`], if there is one. pub fn service_parent(&self) -> anyhow::Result<IoObject> { let mut parent: io_registry_entry_t = 0; @@ -65,7 +66,8 @@ impl IoObject { // pub fn conforms_to_block_storage_driver(&self) -> bool { // // SAFETY: IOKit call, the arguments should be safe. // let result = - // unsafe { IOObjectConformsTo(self.0, "IOBlockStorageDriver\0".as_ptr().cast()) }; + // unsafe { IOObjectConformsTo(self.0, + // "IOBlockStorageDriver\0".as_ptr().cast()) }; // result != 0 // } @@ -79,7 +81,8 @@ impl From<io_object_t> for IoObject { impl Drop for IoObject { fn drop(&mut self) { - // SAFETY: IOKit call, the argument here (an `io_object_t`) should be safe and expected. + // SAFETY: IOKit call, the argument here (an `io_object_t`) should be safe and + // expected. let result = unsafe { IOObjectRelease(self.0) }; assert_eq!(result, kern_return::KERN_SUCCESS); } diff --git a/src/data_collection/disks/unix/other/bindings.rs b/src/data_collection/disks/unix/other/bindings.rs index 0dd4b4fb..3c5739cc 100644 --- a/src/data_collection/disks/unix/other/bindings.rs +++ b/src/data_collection/disks/unix/other/bindings.rs @@ -34,8 +34,9 @@ pub(crate) fn mounts() -> anyhow::Result<Vec<libc::statfs>> { "Expected {expected_len} statfs entries, but instead got {result} entries", ); - // SAFETY: We have a debug assert check, and if `result` is not correct (-1), we check against it. - // Otherwise, getfsstat64 should return the number of statfs structures if it succeeded. + // SAFETY: We have a debug assert check, and if `result` is not correct (-1), we + // check against it. Otherwise, getfsstat64 should return the number of + // statfs structures if it succeeded. // // Source: https://man.freebsd.org/cgi/man.cgi?query=getfsstat&sektion=2&format=html unsafe { diff --git a/src/data_collection/disks/unix/other/partition.rs b/src/data_collection/disks/unix/other/partition.rs index 1e64a70d..ad97bdb4 100644 --- a/src/data_collection/disks/unix/other/partition.rs +++ b/src/data_collection/disks/unix/other/partition.rs @@ -38,7 +38,8 @@ impl Partition { let result = unsafe { libc::statvfs(path.as_ptr(), vfs.as_mut_ptr()) }; if result == 0 { - // SAFETY: We check that it succeeded (result is 0), which means vfs should be populated. + // SAFETY: We check that it succeeded (result is 0), which means vfs should be + // populated. Ok(Usage::new(unsafe { vfs.assume_init() })) } else { bail!("statvfs failed to get the disk usage for disk {path:?}") diff --git a/src/data_collection/disks/unix/usage.rs b/src/data_collection/disks/unix/usage.rs index 3ee70bc4..8b78edb2 100644 --- a/src/data_collection/disks/unix/usage.rs +++ b/src/data_collection/disks/unix/usage.rs @@ -1,7 +1,7 @@ pub struct Usage(libc::statvfs); -// Note that x86 returns `u32` values while x86-64 returns `u64`s, so we convert everything -// to `u64` for consistency. +// Note that x86 returns `u32` values while x86-64 returns `u64`s, so we convert +// everything to `u64` for consistency. #[allow(clippy::useless_conversion)] impl Usage { pub(crate) fn new(vfs: libc::statvfs) -> Self { @@ -13,19 +13,22 @@ impl Usage { u64::from(self.0.f_blocks) * u64::from(self.0.f_frsize) } - /// Returns the available number of bytes used. Note this is not necessarily the same as [`Usage::free`]. + /// Returns the available number of bytes used. Note this is not necessarily + /// the same as [`Usage::free`]. pub fn available(&self) -> u64 { u64::from(self.0.f_bfree) * u64::from(self.0.f_frsize) } #[allow(dead_code)] - /// Returns the total number of bytes used. Equal to `total - available` on Unix. + /// Returns the total number of bytes used. Equal to `total - available` on + /// Unix. pub fn used(&self) -> u64 { let avail_to_root = u64::from(self.0.f_bfree) * u64::from(self.0.f_frsize); self.total() - avail_to_root } - /// Returns the total number of bytes free. Note this is not necessarily the same as [`Usage::available`]. + /// Returns the total number of bytes free. Note this is not necessarily the + /// same as [`Usage::available`]. pub fn free(&self) -> u64 { u64::from(self.0.f_bavail) * u64::from(self.0.f_frsize) } diff --git a/src/data_collection/disks/windows/bindings.rs b/src/data_collection/disks/windows/bindings.rs index c5436145..f5c6b37e 100644 --- a/src/data_collection/disks/windows/bindings.rs +++ b/src/data_collection/disks/windows/bindings.rs @@ -41,7 +41,8 @@ fn volume_io(volume: &Path) -> anyhow::Result<DISK_PERFORMANCE> { wide_path }; - // SAFETY: API call, arguments should be correct. We must also check after the call to ensure it is valid. + // SAFETY: API call, arguments should be correct. We must also check after the + // call to ensure it is valid. let h_device = unsafe { CreateFileW( windows::core::PCWSTR(volume.as_ptr()), @@ -61,7 +62,8 @@ fn volume_io(volume: &Path) -> anyhow::Result<DISK_PERFORMANCE> { let mut disk_performance = DISK_PERFORMANCE::default(); let mut bytes_returned = 0; - // SAFETY: This should be safe, we'll manually check the results and the arguments should be valid. + // SAFETY: This should be safe, we'll manually check the results and the + // arguments should be valid. let ret = unsafe { DeviceIoControl( h_device, @@ -112,7 +114,8 @@ pub(crate) fn all_volume_io() -> anyhow::Result<Vec<anyhow::Result<(DISK_PERFORM let mut buffer = [0_u16; Foundation::MAX_PATH as usize]; // Get the first volume and add the stats needed. - // SAFETY: We must verify the handle is correct. If no volume is found, it will be set to `INVALID_HANDLE_VALUE`. + // SAFETY: We must verify the handle is correct. If no volume is found, it will + // be set to `INVALID_HANDLE_VALUE`. let handle = unsafe { FindFirstVolumeW(&mut buffer) }?; if handle.is_invalid() { bail!("Invalid handle value: {:?}", io::Error::last_os_error()); @@ -148,8 +151,8 @@ pub(crate) fn all_volume_io() -> anyhow::Result<Vec<anyhow::Result<(DISK_PERFORM /// Returns the volume name from a mount name if possible. pub(crate) fn volume_name_from_mount(mount: &str) -> anyhow::Result<String> { - // According to winapi docs 50 is a reasonable length to accomodate the volume path - // https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getvolumenameforvolumemountpointw + // According to winapi docs 50 is a reasonable length to accomodate the volume + // path https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getvolumenameforvolumemountpointw const VOLUME_MAX_LEN: usize = 50; let mount = { diff --git a/src/data_collection/disks/zfs_io_counters.rs b/src/data_collection/disks/zfs_io_counters.rs index a577915a..cb15207b 100644 --- a/src/data_collection/disks/zfs_io_counters.rs +++ b/src/data_collection/disks/zfs_io_counters.rs @@ -1,6 +1,7 @@ use crate::data_collection::disks::IoCounters; -/// Returns zpool I/O stats. Pulls data from `sysctl kstat.zfs.{POOL}.dataset.{objset-*}` +/// Returns zpool I/O stats. Pulls data from `sysctl +/// kstat.zfs.{POOL}.dataset.{objset-*}` #[cfg(target_os = "freebsd")] pub fn zfs_io_stats() -> anyhow::Result<Vec<IoCounters>> { use sysctl::Sysctl; diff --git a/src/data_collection/memory.rs b/src/data_collection/memory.rs index 98cfebda..b953e2bf 100644 --- a/src/data_collection/memory.rs +++ b/src/data_collection/memory.rs @@ -19,5 +19,6 @@ pub mod arc; pub struct MemHarvest { pub used_bytes: u64, pub total_bytes: u64, - pub use_percent: Option<f64>, // TODO: Might be find to just make this an f64, and any consumer checks NaN. + pub use_percent: Option<f64>, /* TODO: Might be find to just make this an f64, and any + * consumer checks NaN. */ } diff --git a/src/data_collection/memory/sysinfo.rs b/src/data_collection/memory/sysinfo.rs index 330bb9b0..1f5606de 100644 --- a/src/data_collection/memory/sysinfo.rs +++ b/src/data_collection/memory/sysinfo.rs @@ -36,11 +36,12 @@ pub(crate) fn get_swap_usage(sys: &System) -> Option<MemHarvest> { }) } -/// Returns cache usage. sysinfo has no way to do this directly but it should equal the difference -/// between the available and free memory. Free memory is defined as memory not containing any data, -/// which means cache and buffer memory are not "free". Available memory is defined as memory able -/// to be allocated by processes, which includes cache and buffer memory. On Windows, this will -/// always be 0. For more information, see [docs](https://docs.rs/sysinfo/latest/sysinfo/struct.System.html#method.available_memory) +/// Returns cache usage. sysinfo has no way to do this directly but it should +/// equal the difference between the available and free memory. Free memory is +/// defined as memory not containing any data, which means cache and buffer +/// memory are not "free". Available memory is defined as memory able +/// to be allocated by processes, which includes cache and buffer memory. On +/// Windows, this will always be 0. For more information, see [docs](https://docs.rs/sysinfo/latest/sysinfo/struct.System.html#method.available_memory) /// and [memory explanation](https://askubuntu.com/questions/867068/what-is-available-memory-while-using-free-command) #[cfg(not(target_os = "windows"))] pub(crate) fn get_cache_usage(sys: &System) -> Option<MemHarvest> { diff --git a/src/data_collection/network/sysinfo.rs b/src/data_collection/network/sysinfo.rs index 3949c4b8..f29180b0 100644 --- a/src/data_collection/network/sysinfo.rs +++ b/src/data_collection/network/sysinfo.rs @@ -7,7 +7,8 @@ use sysinfo::Networks; use super::NetworkHarvest; use crate::app::filter::Filter; -// TODO: Eventually make it so that this thing also takes individual usage into account, so we can show per-interface! +// TODO: Eventually make it so that this thing also takes individual usage into +// account, so we can show per-interface! pub fn get_network_data( networks: &Networks, prev_net_access_time: Instant, prev_net_rx: &mut u64, prev_net_tx: &mut u64, curr_time: Instant, filter: &Option<Filter>, diff --git a/src/data_collection/processes.rs b/src/data_collection/processes.rs index 52caabb7..7359ede2 100644 --- a/src/data_collection/processes.rs +++ b/src/data_collection/processes.rs @@ -87,7 +87,8 @@ pub struct ProcessHarvest { /// Cumulative process uptime. pub time: Duration, - /// This is the *effective* user ID of the process. This is only used on Unix platforms. + /// This is the *effective* user ID of the process. This is only used on + /// Unix platforms. #[cfg(target_family = "unix")] pub uid: Option<libc::uid_t>, diff --git a/src/data_collection/processes/linux.rs b/src/data_collection/processes/linux.rs index 899963d7..87a3a21d 100644 --- a/src/data_collection/processes/linux.rs +++ b/src/data_collection/processes/linux.rs @@ -29,11 +29,11 @@ pub struct PrevProcDetails { cpu_time: u64, } -/// Given `/proc/stat` file contents, determine the idle and non-idle values of the CPU -/// used to calculate CPU usage. +/// Given `/proc/stat` file contents, determine the idle and non-idle values of +/// the CPU used to calculate CPU usage. fn fetch_cpu_usage(line: &str) -> (f64, f64) { - /// Converts a `Option<&str>` value to an f64. If it fails to parse or is `None`, it - /// will return `0_f64`. + /// Converts a `Option<&str>` value to an f64. If it fails to parse or is + /// `None`, it will return `0_f64`. fn str_to_f64(val: Option<&str>) -> f64 { val.and_then(|v| v.parse::<f64>().ok()).unwrap_or(0_f64) } @@ -48,8 +48,8 @@ fn fetch_cpu_usage(line: &str) -> (f64, f64) { let softirq: f64 = str_to_f64(val.next()); let steal: f64 = str_to_f64(val.next()); - // Note we do not get guest/guest_nice, as they are calculated as part of user/nice respectively - // See https://github.com/htop-dev/htop/blob/main/linux/LinuxProcessList.c + // Note we do not get guest/guest_nice, as they are calculated as part of + // user/nice respectively See https://github.com/htop-dev/htop/blob/main/linux/LinuxProcessList.c let idle = idle + iowait; let non_idle = user + nice + system + irq + softirq + steal; @@ -331,8 +331,8 @@ pub(crate) fn linux_process_data( if unnormalized_cpu { let num_processors = collector.sys.system.cpus().len() as f64; - // Note we *divide* here because the later calculation divides `cpu_usage` - in effect, - // multiplying over the number of cores. + // Note we *divide* here because the later calculation divides `cpu_usage` - in + // effect, multiplying over the number of cores. cpu_usage /= num_processors; } diff --git a/src/data_collection/processes/linux/process.rs b/src/data_collection/processes/linux/process.rs index d77ff1f7..45ed80a6 100644 --- a/src/data_collection/processes/linux/process.rs +++ b/src/data_collection/processes/linux/process.rs @@ -29,7 +29,8 @@ fn next_part<'a>(iter: &mut impl Iterator<Item = &'a str>) -> Result<&'a str, io /// A wrapper around the data in `/proc/<PID>/stat`. For documentation, see /// [here](https://man7.org/linux/man-pages/man5/proc.5.html). /// -/// Note this does not necessarily get all fields, only the ones we use in bottom. +/// Note this does not necessarily get all fields, only the ones we use in +/// bottom. pub(crate) struct Stat { /// The filename of the executable without parentheses. pub comm: String, @@ -40,13 +41,16 @@ pub(crate) struct Stat { /// The parent process PID. pub ppid: Pid, - /// The amount of time this process has been scheduled in user mode in clock ticks. + /// The amount of time this process has been scheduled in user mode in clock + /// ticks. pub utime: u64, - /// The amount of time this process has been scheduled in kernel mode in clock ticks. + /// The amount of time this process has been scheduled in kernel mode in + /// clock ticks. pub stime: u64, - /// The resident set size, or the number of pages the process has in real memory. + /// The resident set size, or the number of pages the process has in real + /// memory. pub rss: u64, /// The start time of the process, represented in clock ticks. @@ -56,8 +60,8 @@ pub(crate) struct Stat { impl Stat { #[inline] fn from_file(mut f: File, buffer: &mut String) -> anyhow::Result<Stat> { - // Since this is just one line, we can read it all at once. However, since it might have non-utf8 characters, - // we can't just use read_to_string. + // Since this is just one line, we can read it all at once. However, since it + // might have non-utf8 characters, we can't just use read_to_string. f.read_to_end(unsafe { buffer.as_mut_vec() })?; let line = buffer.to_string_lossy(); @@ -82,12 +86,14 @@ impl Stat { .ok_or_else(|| anyhow!("missing state"))?; let ppid: Pid = next_part(&mut rest)?.parse()?; - // Skip 9 fields until utime (pgrp, session, tty_nr, tpgid, flags, minflt, cminflt, majflt, cmajflt). + // Skip 9 fields until utime (pgrp, session, tty_nr, tpgid, flags, minflt, + // cminflt, majflt, cmajflt). let mut rest = rest.skip(9); let utime: u64 = next_part(&mut rest)?.parse()?; let stime: u64 = next_part(&mut rest)?.parse()?; - // Skip 6 fields until starttime (cutime, cstime, priority, nice, num_threads, itrealvalue). + // Skip 6 fields until starttime (cutime, cstime, priority, nice, num_threads, + // itrealvalue). let mut rest = rest.skip(6); let start_time: u64 = next_part(&mut rest)?.parse()?; @@ -115,7 +121,8 @@ impl Stat { /// A wrapper around the data in `/proc/<PID>/io`. /// -/// Note this does not necessarily get all fields, only the ones we use in bottom. +/// Note this does not necessarily get all fields, only the ones we use in +/// bottom. pub(crate) struct Io { pub read_bytes: u64, pub write_bytes: u64, @@ -136,7 +143,8 @@ impl Io { let mut read_bytes = 0; let mut write_bytes = 0; - // This saves us from doing a string allocation on each iteration compared to `lines()`. + // This saves us from doing a string allocation on each iteration compared to + // `lines()`. while let Ok(bytes) = reader.read_line(buffer) { if bytes > 0 { if buffer.is_empty() { @@ -207,12 +215,13 @@ fn reset(root: &mut PathBuf, buffer: &mut String) { } impl Process { - /// Creates a new [`Process`] given a `/proc/<PID>` path. This may fail if the process - /// no longer exists or there are permissions issues. + /// Creates a new [`Process`] given a `/proc/<PID>` path. This may fail if + /// the process no longer exists or there are permissions issues. /// - /// Note that this pre-allocates fields on **creation**! As such, some data might end - /// up "outdated" depending on when you call some of the methods. Therefore, this struct - /// is only useful for either fields that are unlikely to change, or are short-lived and + /// Note that this pre-allocates fields on **creation**! As such, some data + /// might end up "outdated" depending on when you call some of the + /// methods. Therefore, this struct is only useful for either fields + /// that are unlikely to change, or are short-lived and /// will be discarded quickly. pub(crate) fn from_path(pid_path: PathBuf) -> anyhow::Result<Process> { // TODO: Pass in a buffer vec/string to share? @@ -247,7 +256,8 @@ impl Process { let mut root = pid_path; let mut buffer = String::new(); - // NB: Whenever you add a new stat, make sure to pop the root and clear the buffer! + // NB: Whenever you add a new stat, make sure to pop the root and clear the + // buffer! let stat = open_at(&mut root, "stat", &fd).and_then(|file| Stat::from_file(file, &mut buffer))?; reset(&mut root, &mut buffer); @@ -286,8 +296,9 @@ fn cmdline(root: &mut PathBuf, fd: &OwnedFd, buffer: &mut String) -> anyhow::Res .map_err(Into::into) } -/// Opens a path. Note that this function takes in a mutable root - this will mutate it to avoid allocations. You -/// probably will want to pop the most recent child after if you need to use the buffer again. +/// Opens a path. Note that this function takes in a mutable root - this will +/// mutate it to avoid allocations. You probably will want to pop the most +/// recent child after if you need to use the buffer again. #[inline] fn open_at(root: &mut PathBuf, child: &str, fd: &OwnedFd) -> anyhow::Result<File> { root.push(child); diff --git a/src/data_collection/processes/macos.rs b/src/data_collection/processes/macos.rs index e04b6ae8..46f3273a 100644 --- a/src/data_collection/processes/macos.rs +++ b/src/data_collection/processes/macos.rs @@ -22,7 +22,8 @@ impl UnixProcessExt for MacOSProcessExt { let output = Command::new("ps") .args(["-o", "pid=,pcpu=", "-p"]) .arg( - // Has to look like this since otherwise, it you hit a `unstable_name_collisions` warning. + // Has to look like this since otherwise, it you hit a `unstable_name_collisions` + // warning. Itertools::intersperse(pids.iter().map(i32::to_string), ",".to_string()) .collect::<String>(), ) diff --git a/src/data_collection/processes/macos/sysctl_bindings.rs b/src/data_collection/processes/macos/sysctl_bindings.rs index f9ff358a..53a7f3de 100644 --- a/src/data_collection/processes/macos/sysctl_bindings.rs +++ b/src/data_collection/processes/macos/sysctl_bindings.rs @@ -1,5 +1,5 @@ -//! Partial bindings from Apple's open source code for getting process information. -//! Some of this is based on [heim's binding implementation](https://github.com/heim-rs/heim/blob/master/heim-process/src/sys/macos/bindings/process.rs). +//! Partial bindings from Apple's open source code for getting process +//! information. Some of this is based on [heim's binding implementation](https://github.com/heim-rs/heim/blob/master/heim-process/src/sys/macos/bindings/process.rs). use std::mem; @@ -152,7 +152,8 @@ pub(crate) struct extern_proc { /// Pointer to process group. Originally a pointer to a `pgrp`. pub p_pgrp: *mut c_void, - /// Kernel virtual addr of u-area (PROC ONLY). Originally a pointer to a `user`. + /// Kernel virtual addr of u-area (PROC ONLY). Originally a pointer to a + /// `user`. pub p_addr: *mut c_void, /// Exit status for wait; also stop signal. @@ -207,10 +208,12 @@ pub(crate) struct vmspace { #[allow(non_camel_case_types)] #[repr(C)] pub(crate) struct eproc { - /// Address of proc. We just cheat and use a c_void pointer since we aren't using this. + /// Address of proc. We just cheat and use a c_void pointer since we aren't + /// using this. pub e_paddr: *mut c_void, - /// Session pointer. We just cheat and use a c_void pointer since we aren't using this. + /// Session pointer. We just cheat and use a c_void pointer since we aren't + /// using this. pub e_sess: *mut c_void, /// Process credentials @@ -237,7 +240,8 @@ pub(crate) struct eproc { /// tty process group id pub e_tpgid: pid_t, - /// tty session pointer. We just cheat and use a c_void pointer since we aren't using this. + /// tty session pointer. We just cheat and use a c_void pointer since we + /// aren't using this. pub e_tsess: *mut c_void, /// wchan message @@ -291,8 +295,8 @@ pub(crate) fn kinfo_process(pid: Pid) -> Result<kinfo_proc> { bail!("failed to get process for pid {pid}"); } - // SAFETY: info is initialized if result succeeded and returned a non-negative result. If sysctl failed, it returns - // -1 with errno set. + // SAFETY: info is initialized if result succeeded and returned a non-negative + // result. If sysctl failed, it returns -1 with errno set. // // Source: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/sysctl.3.html unsafe { Ok(info.assume_init()) } diff --git a/src/data_collection/processes/unix/process_ext.rs b/src/data_collection/processes/unix/process_ext.rs index 07077ee2..eeef07ce 100644 --- a/src/data_collection/processes/unix/process_ext.rs +++ b/src/data_collection/processes/unix/process_ext.rs @@ -6,7 +6,10 @@ use hashbrown::HashMap; use sysinfo::{ProcessStatus, System}; use super::ProcessHarvest; -use crate::{data_collection::processes::UserTable, data_collection::Pid, utils::error}; +use crate::{ + data_collection::{processes::UserTable, Pid}, + utils::error, +}; pub(crate) trait UnixProcessExt { fn sysinfo_process_data( diff --git a/src/data_collection/processes/unix/user_table.rs b/src/data_collection/processes/unix/user_table.rs index 6245a858..b36008ea 100644 --- a/src/data_collection/processes/unix/user_table.rs +++ b/src/data_collection/processes/unix/user_table.rs @@ -12,7 +12,8 @@ impl UserTable { if let Some(user) = self.uid_user_mapping.get(&uid) { Ok(user.clone()) } else { - // SAFETY: getpwuid returns a null pointer if no passwd entry is found for the uid + // SAFETY: getpwuid returns a null pointer if no passwd entry is found for the + // uid let passwd = unsafe { libc::getpwuid(uid) }; if passwd.is_null() { diff --git a/src/data_collection/processes/windows.rs b/src/data_collection/processes/windows.rs index d8e3fea9..8d59f515 100644 --- a/src/data_collection/processes/windows.rs +++ b/src/data_collection/processes/windows.rs @@ -103,8 +103,9 @@ pub fn sysinfo_process_data( .and_then(|uid| users.get_user_by_id(uid)) .map_or_else(|| "N/A".into(), |user| user.name().to_owned().into()), time: if process_val.start_time() == 0 { - // Workaround for Windows occasionally returning a start time equal to UNIX epoch, giving a run time - // in the range of 50+ years. We just return a time of zero in this case for simplicity. + // Workaround for Windows occasionally returning a start time equal to UNIX + // epoch, giving a run time in the range of 50+ years. We just + // return a time of zero in this case for simplicity. Duration::ZERO } else { Duration::from_secs(process_val.run_time()) diff --git a/src/data_collection/temperature.rs b/src/data_collection/temperature.rs index b85d1f81..34130ab6 100644 --- a/src/data_collection/temperature.rs +++ b/src/data_collection/temperature.rs @@ -47,7 +47,8 @@ impl FromStr for TemperatureType { } impl TemperatureType { - /// Given a temperature in Celsius, covert it if necessary for a different unit. + /// Given a temperature in Celsius, covert it if necessary for a different + /// unit. pub fn convert_temp_unit(&self, temp_celsius: f32) -> f32 { fn convert_celsius_to_kelvin(celsius: f32) -> f32 { celsius + 273.15 diff --git a/src/data_collection/temperature/linux.rs b/src/data_collection/temperature/linux.rs index 50b858d1..ef920e6d 100644 --- a/src/data_collection/temperature/linux.rs +++ b/src/data_collection/temperature/linux.rs @@ -13,13 +13,15 @@ use crate::{app::filter::Filter, utils::error::BottomError}; const EMPTY_NAME: &str = "Unknown"; -/// Returned results from grabbing hwmon/coretemp temperature sensor values/names. +/// Returned results from grabbing hwmon/coretemp temperature sensor +/// values/names. struct HwmonResults { temperatures: Vec<TempHarvest>, num_hwmon: usize, } -/// Parses and reads temperatures that were in millidegree Celsius, and if successful, returns a temperature in Celsius. +/// Parses and reads temperatures that were in millidegree Celsius, and if +/// successful, returns a temperature in Celsius. fn parse_temp(path: &Path) -> Result<f32> { Ok(fs::read_to_string(path)? .trim_end() @@ -28,7 +30,8 @@ fn parse_temp(path: &Path) -> Result<f32> { / 1_000.0) } -/// Get all candidates from hwmon and coretemp. It will also return the number of entries from hwmon. +/// Get all candidates from hwmon and coretemp. It will also return the number +/// of entries from hwmon. fn get_hwmon_candidates() -> (HashSet<PathBuf>, usize) { let mut dirs = HashSet::default(); @@ -36,11 +39,13 @@ fn get_hwmon_candidates() -> (HashSet<PathBuf>, usize) { for entry in read_dir.flatten() { let mut path = entry.path(); - // hwmon includes many sensors, we only want ones with at least one temperature sensor - // Reading this file will wake the device, but we're only checking existence, so it should be fine. + // hwmon includes many sensors, we only want ones with at least one temperature + // sensor Reading this file will wake the device, but we're only + // checking existence, so it should be fine. if !path.join("temp1_input").exists() { - // Note we also check for a `device` subdirectory (e.g. `/sys/class/hwmon/hwmon*/device/`). - // This is needed for CentOS, which adds this extra `/device` directory. See: + // Note we also check for a `device` subdirectory (e.g. + // `/sys/class/hwmon/hwmon*/device/`). This is needed for + // CentOS, which adds this extra `/device` directory. See: // - https://github.com/nicolargo/glances/issues/1060 // - https://github.com/giampaolo/psutil/issues/971 // - https://github.com/giampaolo/psutil/blob/642438375e685403b4cd60b0c0e25b80dd5a813d/psutil/_pslinux.py#L1316 @@ -65,8 +70,9 @@ fn get_hwmon_candidates() -> (HashSet<PathBuf>, usize) { let path = entry.path(); if path.join("temp1_input").exists() { - // It's possible that there are dupes (represented by symlinks) - the easy - // way is to just substitute the parent directory and check if the hwmon + // It's possible that there are dupes (represented by symlinks) - the + // easy way is to just substitute the parent + // directory and check if the hwmon // variant exists already in a set. // // For more info, see https://github.com/giampaolo/psutil/pull/1822/files @@ -175,7 +181,8 @@ fn finalize_name( } /// Whether the temperature should *actually* be read during enumeration. -/// Will return false if the state is not D0/unknown, or if it does not support `device/power_state`. +/// Will return false if the state is not D0/unknown, or if it does not support +/// `device/power_state`. #[inline] fn is_device_awake(path: &Path) -> bool { // Whether the temperature should *actually* be read during enumeration. @@ -186,10 +193,12 @@ fn is_device_awake(path: &Path) -> bool { if power_state.exists() { if let Ok(state) = fs::read_to_string(power_state) { let state = state.trim(); - // The zenpower3 kernel module (incorrectly?) reports "unknown", causing this check - // to fail and temperatures to appear as zero instead of having the file not exist. + // The zenpower3 kernel module (incorrectly?) reports "unknown", causing this + // check to fail and temperatures to appear as zero instead of + // having the file not exist. // - // Their self-hosted git instance has disabled sign up, so this bug cant be reported either. + // Their self-hosted git instance has disabled sign up, so this bug cant be + // reported either. state == "D0" || state == "unknown" } else { true @@ -199,9 +208,10 @@ fn is_device_awake(path: &Path) -> bool { } } -/// Get temperature sensors from the linux sysfs interface `/sys/class/hwmon` and -/// `/sys/devices/platform/coretemp.*`. It returns all found temperature sensors, and the number -/// of checked hwmon directories (not coretemp directories). +/// Get temperature sensors from the linux sysfs interface `/sys/class/hwmon` +/// and `/sys/devices/platform/coretemp.*`. It returns all found temperature +/// sensors, and the number of checked hwmon directories (not coretemp +/// directories). /// /// For more details, see the relevant Linux kernel documentation: /// - [`/sys/class/hwmon`](https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-class-hwmon) @@ -233,7 +243,8 @@ fn hwmon_temperatures(temp_type: &TemperatureType, filter: &Option<Filter>) -> H // will not wake the device, and thus not block, // and meaning no sensors have to be hidden depending on `power_state` // - // It would probably be more ideal to use a proper async runtime; this would also allow easy cancellation/timeouts. + // It would probably be more ideal to use a proper async runtime; this would + // also allow easy cancellation/timeouts. for file_path in dirs { let sensor_name = read_to_string_lossy(file_path.join("name")); @@ -264,18 +275,19 @@ fn hwmon_temperatures(temp_type: &TemperatureType, filter: &Option<Filter>) -> H // Do some messing around to get a more sensible name for sensors: // - For GPUs, this will use the kernel device name, ex `card0` - // - For nvme drives, this will also use the kernel name, ex `nvme0`. - // This is found differently than for GPUs + // - For nvme drives, this will also use the kernel name, ex `nvme0`. This is + // found differently than for GPUs // - For whatever acpitz is, on my machine this is now `thermal_zone0`. // - For k10temp, this will still be k10temp, but it has to be handled special. let hwmon_name = { let device = file_path.join("device"); - // This will exist for GPUs but not others, this is how we find their kernel name. + // This will exist for GPUs but not others, this is how we find their kernel + // name. let drm = device.join("drm"); if drm.exists() { - // This should never actually be empty. If it is though, we'll fall back to the sensor name - // later on. + // This should never actually be empty. If it is though, we'll fall back to + // the sensor name later on. let mut gpu = None; if let Ok(cards) = drm.read_dir() { @@ -294,10 +306,11 @@ fn hwmon_temperatures(temp_type: &TemperatureType, filter: &Option<Filter>) -> H gpu } else { - // This little mess is to account for stuff like k10temp. This is needed because the - // `device` symlink points to `nvme*` for nvme drives, but to PCI buses for anything - // else. If the first character is alphabetic, it's an actual name like k10temp or - // nvme0, not a PCI bus. + // This little mess is to account for stuff like k10temp. This is needed + // because the `device` symlink points to `nvme*` + // for nvme drives, but to PCI buses for anything + // else. If the first character is alphabetic, it's an actual name like + // k10temp or nvme0, not a PCI bus. fs::read_link(device).ok().and_then(|link| { let link = link .file_name() @@ -316,7 +329,8 @@ fn hwmon_temperatures(temp_type: &TemperatureType, filter: &Option<Filter>) -> H let name = finalize_name(hwmon_name, sensor_label, &sensor_name, &mut seen_names); - // TODO: It's possible we may want to move the filter check further up to avoid probing hwmon if not needed? + // TODO: It's possible we may want to move the filter check further up to avoid + // probing hwmon if not needed? if is_temp_filtered(filter, &name) { if let Ok(temp_celsius) = parse_temp(&temp_path) { temperatures.push(TempHarvest { @@ -335,8 +349,9 @@ fn hwmon_temperatures(temp_type: &TemperatureType, filter: &Option<Filter>) -> H } } -/// Gets data from `/sys/class/thermal/thermal_zone*`. This should only be used if -/// [`hwmon_temperatures`] doesn't return anything to avoid duplicate sensor results. +/// Gets data from `/sys/class/thermal/thermal_zone*`. This should only be used +/// if [`hwmon_temperatures`] doesn't return anything to avoid duplicate sensor +/// results. /// /// See [the Linux kernel documentation](https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-class-thermal) /// for more details. diff --git a/src/data_collection/temperature/sysinfo.rs b/src/data_collection/temperature/sysinfo.rs index c6feb0b8..9e6e797e 100644 --- a/src/data_collection/temperature/sysinfo.rs +++ b/src/data_collection/temperature/sysinfo.rs @@ -21,7 +21,8 @@ pub fn get_temperature_data( } } - // For RockPro64 boards on FreeBSD, they apparently use "hw.temperature" for sensors. + // For RockPro64 boards on FreeBSD, they apparently use "hw.temperature" for + // sensors. #[cfg(target_os = "freebsd")] { use sysctl::Sysctl; diff --git a/src/data_conversion.rs b/src/data_conversion.rs index e08503ec..d72fae24 100644 --- a/src/data_conversion.rs +++ b/src/data_conversion.rs @@ -74,7 +74,8 @@ pub struct ConvertedData { pub cache_labels: Option<(String, String)>, pub swap_labels: Option<(String, String)>, - pub mem_data: Vec<Point>, /* TODO: Switch this and all data points over to a better data structure... */ + pub mem_data: Vec<Point>, /* TODO: Switch this and all data points over to a better data + * structure... */ #[cfg(not(target_os = "windows"))] pub cache_data: Vec<Point>, pub swap_data: Vec<Point>, @@ -169,7 +170,8 @@ impl ConvertedData { data, last_entry, } => { - // A bit faster to just update all the times, so we just clear the vector. + // A bit faster to just update all the times, so we just clear the + // vector. data.clear(); *last_entry = *cpu_usage; } @@ -177,8 +179,8 @@ impl ConvertedData { } } - // TODO: [Opt] Can probably avoid data deduplication - store the shift + data + original once. - // Now push all the data. + // TODO: [Opt] Can probably avoid data deduplication - store the shift + data + + // original once. Now push all the data. for (itx, mut cpu) in &mut self.cpu_data.iter_mut().skip(1).enumerate() { match &mut cpu { CpuWidgetData::All => unreachable!(), @@ -262,10 +264,12 @@ pub fn convert_swap_data_points(data: &DataCollection) -> Vec<Point> { result } -/// Returns the most appropriate binary prefix unit type (e.g. kibibyte) and denominator for the given amount of bytes. +/// Returns the most appropriate binary prefix unit type (e.g. kibibyte) and +/// denominator for the given amount of bytes. /// -/// The expected usage is to divide out the given value with the returned denominator in order to be able to use it -/// with the returned binary unit (e.g. divide 3000 bytes by 1024 to have a value in KiB). +/// The expected usage is to divide out the given value with the returned +/// denominator in order to be able to use it with the returned binary unit +/// (e.g. divide 3000 bytes by 1024 to have a value in KiB). #[inline] fn get_binary_unit_and_denominator(bytes: u64) -> (&'static str, f64) { match bytes { @@ -277,7 +281,8 @@ fn get_binary_unit_and_denominator(bytes: u64) -> (&'static str, f64) { } } -/// Returns the unit type and denominator for given total amount of memory in kibibytes. +/// Returns the unit type and denominator for given total amount of memory in +/// kibibytes. pub fn convert_mem_label(harvest: &MemHarvest) -> Option<(String, String)> { if harvest.total_bytes > 0 { Some((format!("{:3.0}%", harvest.use_percent.unwrap_or(0.0)), { @@ -371,7 +376,9 @@ pub fn convert_network_points( let (rx_converted_result, total_rx_converted_result): ((f64, String), (f64, &'static str)) = if use_binary_prefix { ( - get_binary_prefix(rx_data, unit), /* If this isn't obvious why there's two functions, one you can configure the unit, the other is always bytes */ + get_binary_prefix(rx_data, unit), /* If this isn't obvious why there's two + * functions, one you can configure the unit, + * the other is always bytes */ get_binary_bytes(total_rx_data), ) } else { @@ -464,8 +471,9 @@ pub fn convert_network_points( } } -/// Returns a string given a value that is converted to the closest binary variant. -/// If the value is greater than a gibibyte, then it will return a decimal place. +/// Returns a string given a value that is converted to the closest binary +/// variant. If the value is greater than a gibibyte, then it will return a +/// decimal place. pub fn binary_byte_string(value: u64) -> String { let converted_values = get_binary_bytes(value); if value >= GIBI_LIMIT { @@ -486,8 +494,9 @@ pub fn dec_bytes_per_string(value: u64) -> String { } } -/// Returns a string given a value that is converted to the closest SI-variant, per second. -/// If the value is greater than a giga-X, then it will return a decimal place. +/// Returns a string given a value that is converted to the closest SI-variant, +/// per second. If the value is greater than a giga-X, then it will return a +/// decimal place. pub fn dec_bytes_per_second_string(value: u64) -> String { let converted_values = get_decimal_bytes(value); if value >= GIGA_LIMIT { diff --git a/src/main.rs b/src/main.rs index ca4656e1..d22e62a7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,11 @@ -//! A customizable cross-platform graphical process/system monitor for the terminal. -//! Supports Linux, macOS, and Windows. Inspired by gtop, gotop, and htop. +//! A customizable cross-platform graphical process/system monitor for the +//! terminal. Supports Linux, macOS, and Windows. Inspired by gtop, gotop, and +//! htop. //! -//! **Note:** The following documentation is primarily intended for people to refer to for development purposes rather -//! than the actual usage of the application. If you are instead looking for documentation regarding the *usage* of -//! bottom, refer to [here](https://clementtsang.github.io/bottom/stable/). +//! **Note:** The following documentation is primarily intended for people to +//! refer to for development purposes rather than the actual usage of the +//! application. If you are instead looking for documentation regarding the +//! *usage* of bottom, refer to [here](https://clementtsang.github.io/bottom/stable/). pub mod app; pub mod utils { @@ -48,8 +50,7 @@ use crossterm::{ }; use data_conversion::*; use event::{handle_key_event_or_break, handle_mouse_event, BottomEvent, CollectionThreadEvent}; -use options::args; -use options::{get_color_scheme, get_config_path, get_or_create_config, init_app}; +use options::{args, get_color_scheme, get_config_path, get_or_create_config, init_app}; use tui::{backend::CrosstermBackend, Terminal}; use utils::error; #[allow(unused_imports)] @@ -156,9 +157,11 @@ fn create_input_thread( if let Ok(event) = read() { match event { Event::Resize(_, _) => { - // TODO: Might want to debounce this in the future, or take into account the actual resize - // values. Maybe we want to keep the current implementation in case the resize event might - // not fire... not sure. + // TODO: Might want to debounce this in the future, or take into + // account the actual resize values. + // Maybe we want to keep the current implementation in case the + // resize event might not fire... + // not sure. if sender.send(BottomEvent::Resize).is_err() { break; @@ -170,7 +173,8 @@ fn create_input_thread( } } Event::Key(key) if key.kind == KeyEventKind::Press => { - // For now, we only care about key down events. This may change in the future. + // For now, we only care about key down events. This may change in + // the future. if sender.send(BottomEvent::KeyInput(key)).is_err() { break; } @@ -302,7 +306,8 @@ fn main() -> anyhow::Result<()> { CanvasStyling::new(colour_scheme, &config)? }; - // Create an "app" struct, which will control most of the program and store settings/state + // Create an "app" struct, which will control most of the program and store + // settings/state let (mut app, widget_layout) = init_app(args, config, &styling)?; // Create painter and set colours. @@ -311,14 +316,16 @@ fn main() -> anyhow::Result<()> { // Check if the current environment is in a terminal. check_if_terminal(); - // Create termination mutex and cvar. We use this setup because we need to sleep at some points in the update - // thread, but we want to be able to interrupt the "sleep" if a termination occurs. + // Create termination mutex and cvar. We use this setup because we need to sleep + // at some points in the update thread, but we want to be able to interrupt + // the "sleep" if a termination occurs. let termination_lock = Arc::new(Mutex::new(false)); let termination_cvar = Arc::new(Condvar::new()); let (sender, receiver) = mpsc::channel(); - // Set up the event loop thread; we set this up early to speed up first-time-to-data. + // Set up the event loop thread; we set this up early to speed up + // first-time-to-data. let (collection_thread_ctrl_sender, collection_thread_ctrl_receiver) = mpsc::channel(); let _collection_thread = create_collection_thread( sender.clone(), @@ -394,7 +401,8 @@ fn main() -> anyhow::Result<()> { let mut first_run = true; - // Draw once first to initialize the canvas, so it doesn't feel like it's frozen. + // Draw once first to initialize the canvas, so it doesn't feel like it's + // frozen. try_drawing(&mut terminal, &mut app, &mut painter)?; loop { diff --git a/src/options.rs b/src/options.rs index 998cc285..345981bd 100644 --- a/src/options.rs +++ b/src/options.rs @@ -16,7 +16,7 @@ use std::{ }; use anyhow::{Context, Result}; -pub use colours::ConfigColours; +pub use colours::ColoursConfig; pub use config::ConfigV1; use hashbrown::{HashMap, HashSet}; use indexmap::IndexSet; @@ -62,9 +62,10 @@ macro_rules! is_flag_enabled { }; } -/// Returns the config path to use. If `override_config_path` is specified, then we will use -/// that. If not, then return the "default" config path, which is: -/// - If a path already exists at `<HOME>/bottom/bottom.toml`, then use that for legacy reasons. +/// Returns the config path to use. If `override_config_path` is specified, then +/// we will use that. If not, then return the "default" config path, which is: +/// - If a path already exists at `<HOME>/bottom/bottom.toml`, then use that for +/// legacy reasons. /// - Otherwise, use `<SYSTEM_CONFIG_FOLDER>/bottom/bottom.toml`. /// /// For more details on this, see [dirs](https://docs.rs/dirs/latest/dirs/fn.config_dir.html)' @@ -93,9 +94,10 @@ pub fn get_config_path(override_config_path: Option<&Path>) -> Option<PathBuf> { }) } -/// Get the config at `config_path`. If there is no config file at the specified path, it will -/// try to create a new file with the default settings, and return the default config. If bottom -/// fails to write a new config, it will silently just return the default config. +/// Get the config at `config_path`. If there is no config file at the specified +/// path, it will try to create a new file with the default settings, and return +/// the default config. If bottom fails to write a new config, it will silently +/// just return the default config. pub fn get_or_create_config(config_path: Option<&Path>) -> error::Result<ConfigV1> { match &config_path { Some(path) => { @@ -111,7 +113,8 @@ pub fn get_or_create_config(config_path: Option<&Path>) -> error::Result<ConfigV } } None => { - // If we somehow don't have any config path, then just assume the default config but don't write to any file. + // If we somehow don't have any config path, then just assume the default config + // but don't write to any file. // // TODO: Maybe make this "show" an error, but don't crash. Ok(ConfigV1::default()) @@ -124,7 +127,8 @@ pub fn init_app( ) -> Result<(App, BottomLayout)> { use BottomWidgetType::*; - // Since everything takes a reference, but we want to take ownership here to drop matches/config later... + // Since everything takes a reference, but we want to take ownership here to + // drop matches/config later... let args = &args; let config = &config; @@ -175,18 +179,13 @@ pub fn init_app( is_flag_enabled!(network_use_binary_prefix, args.network, config); let proc_columns: Option<IndexSet<ProcWidgetColumn>> = { - let columns = config.processes.as_ref().map(|cfg| cfg.columns.clone()); - - match columns { - Some(columns) => { - if columns.is_empty() { - None - } else { - Some(IndexSet::from_iter(columns)) - } + config.processes.as_ref().and_then(|cfg| { + if cfg.columns.is_empty() { + None + } else { + Some(IndexSet::from_iter(cfg.columns.clone())) } - None => None, - } + }) }; let network_legend_position = get_network_legend_position(args, config)?; @@ -384,14 +383,29 @@ pub fn init_app( use_battery: used_widget_set.get(&Battery).is_some(), }; - let disk_filter = - get_ignore_list(&config.disk_filter).context("Update 'disk_filter' in your config file")?; - let mount_filter = get_ignore_list(&config.mount_filter) - .context("Update 'mount_filter' in your config file")?; - let temp_filter = - get_ignore_list(&config.temp_filter).context("Update 'temp_filter' in your config file")?; - let net_filter = - get_ignore_list(&config.net_filter).context("Update 'net_filter' in your config file")?; + let (disk_name_filter, disk_mount_filter) = { + match &config.disk { + Some(cfg) => { + let df = get_ignore_list(&cfg.name_filter) + .context("Update 'disk.name_filter' in your config file")?; + let mf = get_ignore_list(&cfg.mount_filter) + .context("Update 'disk.mount_filter' in your config file")?; + + (df, mf) + } + None => (None, None), + } + }; + let temp_sensor_filter = match &config.temperature { + Some(cfg) => get_ignore_list(&cfg.sensor_filter) + .context("Update 'temperature.sensor_filter' in your config file")?, + None => None, + }; + let net_interface_filter = match &config.network { + Some(cfg) => get_ignore_list(&cfg.interface_filter) + .context("Update 'network.interface_filter' in your config file")?, + None => None, + }; let states = AppWidgetStates { cpu_state: CpuState::init(cpu_state_map), @@ -406,10 +420,10 @@ pub fn init_app( let current_widget = widget_map.get(&initial_widget_id).unwrap().clone(); let filters = DataFilters { - disk_filter, - mount_filter, - temp_filter, - net_filter, + disk_filter: disk_name_filter, + mount_filter: disk_mount_filter, + temp_filter: temp_sensor_filter, + net_filter: net_interface_filter, }; let is_expanded = expanded && !use_basic_mode; @@ -686,7 +700,8 @@ fn get_default_widget_and_count( fn get_use_battery(args: &BottomArgs, config: &ConfigV1) -> bool { #[cfg(feature = "battery")] { - // TODO: Move this so it's dynamic in the app itself and automatically hide if there are no batteries? + // TODO: Move this so it's dynamic in the app itself and automatically hide if + // there are no batteries? if let Ok(battery_manager) = Manager::new() { if let Ok(batteries) = battery_manager.batteries() { if batteries.count() == 0 { @@ -907,7 +922,7 @@ mod test { args::BottomArgs, canvas::styling::CanvasStyling, options::{ - config::ConfigFlags, get_default_time_value, get_retention, get_update_rate, + config::FlagConfig, get_default_time_value, get_retention, get_update_rate, try_parse_ms, }, }; @@ -986,7 +1001,7 @@ mod test { let args = BottomArgs::parse_from(["btm"]); let mut config = ConfigV1::default(); - let flags = ConfigFlags { + let flags = FlagConfig { time_delta: Some("2 min".to_string().into()), default_time_value: Some("300s".to_string().into()), rate: Some("1s".to_string().into()), @@ -1016,7 +1031,7 @@ mod test { let args = BottomArgs::parse_from(["btm"]); let mut config = ConfigV1::default(); - let flags = ConfigFlags { + let flags = FlagConfig { time_delta: Some("120000".to_string().into()), default_time_value: Some("300000".to_string().into()), rate: Some("1000".to_string().into()), @@ -1046,7 +1061,7 @@ mod test { let args = BottomArgs::parse_from(["btm"]); let mut config = ConfigV1::default(); - let flags = ConfigFlags { + let flags = FlagConfig { time_delta: Some(120000.into()), default_time_value: Some(300000.into()), rate: Some(1000.into()), @@ -1079,15 +1094,17 @@ mod test { super::init_app(args, config, &styling).unwrap().0 } - // TODO: There's probably a better way to create clap options AND unify together to avoid the possibility of - // typos/mixing up. Use proc macros to unify on one struct? + // TODO: There's probably a better way to create clap options AND unify together + // to avoid the possibility of typos/mixing up. Use proc macros to unify on + // one struct? #[test] fn verify_cli_options_build() { let app = crate::args::build_cmd(); let default_app = create_app(BottomArgs::parse_from(["btm"])); - // Skip battery since it's tricky to test depending on the platform/features we're testing with. + // Skip battery since it's tricky to test depending on the platform/features + // we're testing with. let skip = ["help", "version", "celsius", "battery"]; for arg in app.get_arguments().collect::<Vec<_>>() { diff --git a/src/options/args.rs b/src/options/args.rs index 041ac7ec..27c773d7 100644 --- a/src/options/args.rs +++ b/src/options/args.rs @@ -1,7 +1,7 @@ //! Argument parsing via clap. //! -//! Note that you probably want to keep this as a single file so the build script doesn't -//! trip all over itself. +//! Note that you probably want to keep this as a single file so the build +//! script doesn't trip all over itself. // TODO: New sections are misaligned! See if we can get that fixed. @@ -557,7 +557,8 @@ pub struct StyleArgs { pub color: Option<String>, } -/// Other arguments. This just handle options that are for help/version displaying. +/// Other arguments. This just handle options that are for help/version +/// displaying. #[derive(Args, Clone, Debug)] #[command(next_help_heading = "Other Options", rename_all = "snake_case")] pub struct OtherArgs { diff --git a/src/options/colours.rs b/src/options/colours.rs index edef00b4..e01e2ed0 100644 --- a/src/options/colours.rs +++ b/src/options/colours.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Default, Deserialize, Serialize)] -pub struct ConfigColours { +pub struct ColoursConfig { pub table_header_color: Option<Cow<'static, str>>, pub all_cpu_color: Option<Cow<'static, str>>, pub avg_cpu_color: Option<Cow<'static, str>>, @@ -31,8 +31,9 @@ pub struct ConfigColours { pub low_battery_color: Option<Cow<'static, str>>, } -impl ConfigColours { - /// Returns `true` if there is a [`ConfigColours`] that is empty or there isn't one at all. +impl ColoursConfig { + /// Returns `true` if there is a [`ConfigColours`] that is empty or there + /// isn't one at all. pub fn is_empty(&self) -> bool { if let Ok(serialized_string) = toml_edit::ser::to_string(self) { return serialized_string.is_empty(); diff --git a/src/options/config.rs b/src/options/config.rs index 975472f1..659563bf 100644 --- a/src/options/config.rs +++ b/src/options/config.rs @@ -1,24 +1,29 @@ pub mod cpu; +pub mod disk; mod ignore_list; pub mod layout; -pub mod process_columns; +pub mod network; +pub mod process; +pub mod temperature; +use disk::DiskConfig; +use network::NetworkConfig; use serde::{Deserialize, Serialize}; +use temperature::TempConfig; pub use self::ignore_list::IgnoreList; -use self::{cpu::CpuConfig, layout::Row, process_columns::ProcessConfig}; -use super::ConfigColours; +use self::{cpu::CpuConfig, layout::Row, process::ProcessesConfig}; +use super::ColoursConfig; #[derive(Clone, Debug, Default, Deserialize)] pub struct ConfigV1 { - pub(crate) flags: Option<ConfigFlags>, - pub(crate) colors: Option<ConfigColours>, + pub(crate) flags: Option<FlagConfig>, + pub(crate) colors: Option<ColoursConfig>, pub(crate) row: Option<Vec<Row>>, - pub(crate) disk_filter: Option<IgnoreList>, - pub(crate) mount_filter: Option<IgnoreList>, - pub(crate) temp_filter: Option<IgnoreList>, - pub(crate) net_filter: Option<IgnoreList>, - pub(crate) processes: Option<ProcessConfig>, + pub(crate) processes: Option<ProcessesConfig>, + pub(crate) disk: Option<DiskConfig>, + pub(crate) temperature: Option<TempConfig>, + pub(crate) network: Option<NetworkConfig>, pub(crate) cpu: Option<CpuConfig>, } @@ -42,7 +47,7 @@ impl From<u64> for StringOrNum { } #[derive(Clone, Debug, Default, Deserialize, Serialize)] -pub(crate) struct ConfigFlags { +pub(crate) struct FlagConfig { pub(crate) hide_avg_cpu: Option<bool>, pub(crate) dot_marker: Option<bool>, pub(crate) temperature_type: Option<String>, diff --git a/src/options/config/cpu.rs b/src/options/config/cpu.rs index ce027305..c5af891d 100644 --- a/src/options/config/cpu.rs +++ b/src/options/config/cpu.rs @@ -1,6 +1,7 @@ use serde::Deserialize; -/// The default selection of the CPU widget. If the given selection is invalid, we will fall back to all. +/// The default selection of the CPU widget. If the given selection is invalid, +/// we will fall back to all. #[derive(Clone, Copy, Debug, Default, Deserialize)] #[serde(rename_all = "lowercase")] pub enum CpuDefault { diff --git a/src/options/config/disk.rs b/src/options/config/disk.rs new file mode 100644 index 00000000..355416bc --- /dev/null +++ b/src/options/config/disk.rs @@ -0,0 +1,13 @@ +use serde::Deserialize; + +use super::IgnoreList; + +/// Disk configuration. +#[derive(Clone, Debug, Default, Deserialize)] +pub struct DiskConfig { + /// A filter over the disk names. + pub name_filter: Option<IgnoreList>, + + /// A filter over the mount names. + pub mount_filter: Option<IgnoreList>, +} diff --git a/src/options/config/ignore_list.rs b/src/options/config/ignore_list.rs index 472f7fa6..f0d5325b 100644 --- a/src/options/config/ignore_list.rs +++ b/src/options/config/ignore_list.rs @@ -9,7 +9,8 @@ fn default_as_true() -> bool { pub struct IgnoreList { #[serde(default = "default_as_true")] // TODO: 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"? + // 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<String>, #[serde(default)] diff --git a/src/options/config/network.rs b/src/options/config/network.rs new file mode 100644 index 00000000..7e75fdd4 --- /dev/null +++ b/src/options/config/network.rs @@ -0,0 +1,10 @@ +use serde::Deserialize; + +use super::IgnoreList; + +/// Network configuration. +#[derive(Clone, Debug, Default, Deserialize)] +pub struct NetworkConfig { + /// A filter over the network interface names. + pub interface_filter: Option<IgnoreList>, +} diff --git a/src/options/config/process_columns.rs b/src/options/config/process.rs similarity index 72% rename from src/options/config/process_columns.rs rename to src/options/config/process.rs index ea65bf27..f8bbe866 100644 --- a/src/options/config/process_columns.rs +++ b/src/options/config/process.rs @@ -2,22 +2,23 @@ use serde::Deserialize; use crate::widgets::ProcWidgetColumn; -/// Process column settings. +/// Process configuration. #[derive(Clone, Debug, Default, Deserialize)] -pub struct ProcessConfig { +pub struct ProcessesConfig { + /// A list of process widget columns. #[serde(default)] pub columns: Vec<ProcWidgetColumn>, } #[cfg(test)] mod test { - use super::ProcessConfig; + use super::ProcessesConfig; use crate::widgets::ProcWidgetColumn; #[test] fn empty_column_setting() { let config = ""; - let generated: ProcessConfig = toml_edit::de::from_str(config).unwrap(); + let generated: ProcessesConfig = toml_edit::de::from_str(config).unwrap(); assert!(generated.columns.is_empty()); } @@ -27,7 +28,7 @@ mod test { columns = ["CPU%", "PiD", "user", "MEM", "Tread", "T.Write", "Rps", "W/s", "tiMe", "USER", "state"] "#; - let generated: ProcessConfig = toml_edit::de::from_str(config).unwrap(); + let generated: ProcessesConfig = toml_edit::de::from_str(config).unwrap(); assert_eq!( generated.columns, vec![ @@ -49,25 +50,25 @@ mod test { #[test] fn process_column_settings_2() { let config = r#"columns = ["MEM", "TWrite", "Cpuz", "read", "wps"]"#; - toml_edit::de::from_str::<ProcessConfig>(config).expect_err("Should error out!"); + toml_edit::de::from_str::<ProcessesConfig>(config).expect_err("Should error out!"); } #[test] fn process_column_settings_3() { let config = r#"columns = ["Twrite", "T.Write"]"#; - let generated: ProcessConfig = toml_edit::de::from_str(config).unwrap(); + let generated: ProcessesConfig = toml_edit::de::from_str(config).unwrap(); assert_eq!(generated.columns, vec![ProcWidgetColumn::TotalWrite; 2]); let config = r#"columns = ["Tread", "T.read"]"#; - let generated: ProcessConfig = toml_edit::de::from_str(config).unwrap(); + let generated: ProcessesConfig = toml_edit::de::from_str(config).unwrap(); assert_eq!(generated.columns, vec![ProcWidgetColumn::TotalRead; 2]); let config = r#"columns = ["read", "rps", "r/s"]"#; - let generated: ProcessConfig = toml_edit::de::from_str(config).unwrap(); + let generated: ProcessesConfig = toml_edit::de::from_str(config).unwrap(); assert_eq!(generated.columns, vec![ProcWidgetColumn::ReadPerSecond; 3]); let config = r#"columns = ["write", "wps", "w/s"]"#; - let generated: ProcessConfig = toml_edit::de::from_str(config).unwrap(); + let generated: ProcessesConfig = toml_edit::de::from_str(config).unwrap(); assert_eq!(generated.columns, vec![ProcWidgetColumn::WritePerSecond; 3]); } } diff --git a/src/options/config/temperature.rs b/src/options/config/temperature.rs new file mode 100644 index 00000000..266d7406 --- /dev/null +++ b/src/options/config/temperature.rs @@ -0,0 +1,10 @@ +use serde::Deserialize; + +use super::IgnoreList; + +/// Temperature configuration. +#[derive(Clone, Debug, Default, Deserialize)] +pub struct TempConfig { + /// A filter over the sensor names. + pub sensor_filter: Option<IgnoreList>, +} diff --git a/src/utils/data_prefixes.rs b/src/utils/data_prefixes.rs index 516a66ba..fb2892c4 100644 --- a/src/utils/data_prefixes.rs +++ b/src/utils/data_prefixes.rs @@ -38,9 +38,9 @@ pub const LOG_MEBI_LIMIT_U32: u32 = 20; pub const LOG_GIBI_LIMIT_U32: u32 = 30; pub const LOG_TEBI_LIMIT_U32: u32 = 40; -/// Returns a tuple containing the value and the unit in bytes. In units of 1024. -/// This only supports up to a tebi. Note the "single" unit will have a space appended to match the others if -/// `spacing` is true. +/// Returns a tuple containing the value and the unit in bytes. In units of +/// 1024. This only supports up to a tebi. Note the "single" unit will have a +/// space appended to match the others if `spacing` is true. #[inline] pub fn get_binary_bytes(bytes: u64) -> (f64, &'static str) { match bytes { @@ -52,9 +52,9 @@ pub fn get_binary_bytes(bytes: u64) -> (f64, &'static str) { } } -/// Returns a tuple containing the value and the unit in bytes. In units of 1000. -/// This only supports up to a tera. Note the "single" unit will have a space appended to match the others if -/// `spacing` is true. +/// Returns a tuple containing the value and the unit in bytes. In units of +/// 1000. This only supports up to a tera. Note the "single" unit will have a +/// space appended to match the others if `spacing` is true. #[inline] pub fn get_decimal_bytes(bytes: u64) -> (f64, &'static str) { match bytes { @@ -67,8 +67,8 @@ pub fn get_decimal_bytes(bytes: u64) -> (f64, &'static str) { } /// Returns a tuple containing the value and the unit. In units of 1024. -/// This only supports up to a tebi. Note the "single" unit will have a space appended to match the others if -/// `spacing` is true. +/// This only supports up to a tebi. Note the "single" unit will have a space +/// appended to match the others if `spacing` is true. #[inline] pub fn get_binary_prefix(quantity: u64, unit: &str) -> (f64, String) { match quantity { @@ -81,8 +81,8 @@ pub fn get_binary_prefix(quantity: u64, unit: &str) -> (f64, String) { } /// Returns a tuple containing the value and the unit. In units of 1000. -/// This only supports up to a tera. Note the "single" unit will have a space appended to match the others if -/// `spacing` is true. +/// This only supports up to a tera. Note the "single" unit will have a space +/// appended to match the others if `spacing` is true. #[inline] pub fn get_decimal_prefix(quantity: u64, unit: &str) -> (f64, String) { match quantity { diff --git a/src/utils/general.rs b/src/utils/general.rs index 789c73f5..7191f3cc 100644 --- a/src/utils/general.rs +++ b/src/utils/general.rs @@ -26,12 +26,12 @@ pub fn partial_ordering_desc<T: PartialOrd>(a: T, b: T) -> Ordering { /// A trait for additional clamping functions on numeric types. pub trait ClampExt { - /// Restrict a value by a lower bound. If the current value is _lower_ than `lower_bound`, - /// it will be set to `_lower_bound`. + /// Restrict a value by a lower bound. If the current value is _lower_ than + /// `lower_bound`, it will be set to `_lower_bound`. fn clamp_lower(&self, lower_bound: Self) -> Self; - /// Restrict a value by an upper bound. If the current value is _greater_ than `upper_bound`, - /// it will be set to `upper_bound`. + /// Restrict a value by an upper bound. If the current value is _greater_ + /// than `upper_bound`, it will be set to `upper_bound`. fn clamp_upper(&self, upper_bound: Self) -> Self; } diff --git a/src/utils/logging.rs b/src/utils/logging.rs index 00e7e3db..27fa92fe 100644 --- a/src/utils/logging.rs +++ b/src/utils/logging.rs @@ -13,13 +13,16 @@ pub fn init_logger( let offset = OFFSET.get_or_init(|| { use time::util::local_offset::Soundness; - // SAFETY: We only invoke this once, quickly, and it should be invoked in a single-thread context. - // We also should only ever hit this logging at all in a debug context which is generally fine, + // SAFETY: We only invoke this once, quickly, and it should be invoked in a + // single-thread context. We also should only ever hit this + // logging at all in a debug context which is generally fine, // release builds should have this logging disabled entirely for now. unsafe { - // XXX: If we ever DO add general logging as a release feature, evaluate this again and whether this is - // something we want enabled in release builds! What might be safe is falling back to the non-set-soundness - // mode when specifically using certain feature flags (e.g. dev-logging feature enables this behaviour). + // XXX: If we ever DO add general logging as a release feature, evaluate this + // again and whether this is something we want enabled in + // release builds! What might be safe is falling back to the non-set-soundness + // mode when specifically using certain feature flags (e.g. dev-logging feature + // enables this behaviour). time::util::local_offset::set_soundness(Soundness::Unsound); let res = @@ -39,8 +42,8 @@ pub fn init_logger( "{}[{}][{}] {}", offset_time .format(&time::macros::format_description!( - // The weird "[[[" is because we need to escape a bracket ("[[") to show one "[". - // See https://time-rs.github.io/book/api/format-description.html + // The weird "[[[" is because we need to escape a bracket ("[[") to show + // one "[". See https://time-rs.github.io/book/api/format-description.html "[[[year]-[month]-[day]][[[hour]:[minute]:[second][subsecond digits:9]]" )) .unwrap(), diff --git a/src/utils/strings.rs b/src/utils/strings.rs index 1d970c9f..078f093b 100644 --- a/src/utils/strings.rs +++ b/src/utils/strings.rs @@ -9,11 +9,12 @@ pub fn truncate_to_text<'a, U: Into<usize>>(content: &str, width: U) -> Text<'a> Text::raw(truncate_str(content, width.into())) } -/// Checks that the first string is equal to any of the other ones in a ASCII case-insensitive match. +/// Checks that the first string is equal to any of the other ones in a ASCII +/// case-insensitive match. /// /// The generated code is the same as writing: -/// `to_ascii_lowercase(a) == to_ascii_lowercase(b) || to_ascii_lowercase(a) == to_ascii_lowercase(c)`, -/// but without allocating and copying temporaries. +/// `to_ascii_lowercase(a) == to_ascii_lowercase(b) || to_ascii_lowercase(a) == +/// to_ascii_lowercase(c)`, but without allocating and copying temporaries. /// /// # Examples /// diff --git a/src/widgets.rs b/src/widgets.rs index 9c0a69be..b4f82650 100644 --- a/src/widgets.rs +++ b/src/widgets.rs @@ -15,7 +15,8 @@ pub use process_table::*; pub use temperature_table::*; use tui::{layout::Rect, Frame}; -/// A [`Widget`] converts raw data into something that a user can see and interact with. +/// A [`Widget`] converts raw data into something that a user can see and +/// interact with. pub trait Widget<Data> { /// How to actually draw the widget to the terminal. fn draw(&self, f: &mut Frame<'_>, draw_location: Rect, widget_id: u64); diff --git a/src/widgets/cpu_graph.rs b/src/widgets/cpu_graph.rs index e90ce697..889cf57e 100644 --- a/src/widgets/cpu_graph.rs +++ b/src/widgets/cpu_graph.rs @@ -87,13 +87,13 @@ impl DataToCell<CpuWidgetColumn> for CpuWidgetTableData { let calculated_width = calculated_width.get(); - // This is a bit of a hack, but apparently we can avoid having to do any fancy checks - // of showing the "All" on a specific column if the other is hidden by just always - // showing it on the CPU (first) column - if there isn't room for it, it will just collapse - // down. + // This is a bit of a hack, but apparently we can avoid having to do any fancy + // checks of showing the "All" on a specific column if the other is + // hidden by just always showing it on the CPU (first) column - if there + // isn't room for it, it will just collapse down. // - // This is the same for the use percentages - we just *always* show them, and *always* hide the CPU column if - // it is too small. + // This is the same for the use percentages - we just *always* show them, and + // *always* hide the CPU column if it is too small. match &self { CpuWidgetTableData::All => match column { CpuWidgetColumn::CPU => Some("All".into()), diff --git a/src/widgets/process_table.rs b/src/widgets/process_table.rs index 0b081dec..873a8d8a 100644 --- a/src/widgets/process_table.rs +++ b/src/widgets/process_table.rs @@ -28,7 +28,8 @@ use crate::{ data_collection::processes::{Pid, ProcessHarvest}, }; -/// ProcessSearchState only deals with process' search's current settings and state. +/// ProcessSearchState only deals with process' search's current settings and +/// state. pub struct ProcessSearchState { pub search_state: AppSearchState, pub is_ignoring_case: bool, @@ -172,7 +173,8 @@ pub struct ProcWidgetState { /// The state of the togglable table that controls sorting. pub sort_table: SortTable, - /// The internal column mapping as an [`IndexSet`], to allow us to do quick mappings of column type -> index. + /// The internal column mapping as an [`IndexSet`], to allow us to do quick + /// mappings of column type -> index. pub column_mapping: IndexSet<ProcWidgetColumn>, /// A name-to-pid mapping. @@ -426,8 +428,9 @@ impl ProcWidgetState { /// Update the current table data. /// - /// This function *only* updates the displayed process data. If there is a need to update the actual *stored* data, - /// call it before this function. + /// This function *only* updates the displayed process data. If there is a + /// need to update the actual *stored* data, call it before this + /// function. pub fn set_table_data(&mut self, data_collection: &DataCollection) { let data = match &self.mode { ProcWidgetMode::Grouped | ProcWidgetMode::Normal => { @@ -495,7 +498,8 @@ impl ProcWidgetState { } } - // A process is shown under the filtered tree if at least one of these conditions hold: + // A process is shown under the filtered tree if at least one of these + // conditions hold: // - The process itself matches. // - The process contains some descendant that matches. // - The process's parent (and only parent, not any ancestor) matches. @@ -524,7 +528,8 @@ impl ProcWidgetState { // Show the entry if it is: // - Matches the filter. - // - Has at least one child (doesn't have to be direct) that matches the filter. + // - Has at least one child (doesn't have to be direct) that matches the + // filter. // - Is the child of a shown process. let is_shown = is_process_matching || !shown_children.is_empty() @@ -724,7 +729,8 @@ impl ProcWidgetState { if let Some(grouped_process_harvest) = id_process_mapping.get_mut(id) { grouped_process_harvest.add(process); } else { - // FIXME: [PERF] could maybe eliminate an allocation here in the grouped mode... or maybe just avoid the entire transformation step, making an alloc fine. + // FIXME: [PERF] could maybe eliminate an allocation here in the grouped mode... + // or maybe just avoid the entire transformation step, making an alloc fine. id_process_mapping.insert(id, process.clone()); } } @@ -813,8 +819,8 @@ impl ProcWidgetState { self.force_update_data = true; } - /// Marks the selected column as hidden, and automatically resets the selected column to the default - /// sort index and order. + /// Marks the selected column as hidden, and automatically resets the + /// selected column to the default sort index and order. fn hide_column(&mut self, column: ProcWidgetColumn) { if let Some(index) = self.column_mapping.get_index_of(&column) { if let Some(col) = self.table.columns.get_mut(index) { @@ -837,7 +843,8 @@ impl ProcWidgetState { } } - /// Select a column. If the column is already selected, then just toggle the sort order. + /// Select a column. If the column is already selected, then just toggle the + /// sort order. pub fn select_column(&mut self, column: ProcWidgetColumn) { if let Some(index) = self.column_mapping.get_index_of(&column) { self.table.set_sort_index(index); @@ -891,12 +898,15 @@ impl ProcWidgetState { /// Toggles the appropriate columns/settings when tab is pressed. /// - /// If count is enabled, we should set the mode to [`ProcWidgetMode::Grouped`], and switch off the User and State - /// columns. We should also move the user off of the columns if they were selected, as those columns are now hidden - /// (handled by internal method calls), and go back to the "defaults". + /// If count is enabled, we should set the mode to + /// [`ProcWidgetMode::Grouped`], and switch off the User and State + /// columns. We should also move the user off of the columns if they were + /// selected, as those columns are now hidden (handled by internal + /// method calls), and go back to the "defaults". /// - /// Otherwise, if count is disabled, then if the columns exist, the User and State columns should be re-enabled, - /// and the mode switched to [`ProcWidgetMode::Normal`]. + /// Otherwise, if count is disabled, then if the columns exist, the User and + /// State columns should be re-enabled, and the mode switched to + /// [`ProcWidgetMode::Normal`]. pub fn toggle_tab(&mut self) { if !matches!(self.mode, ProcWidgetMode::Tree { .. }) { if let Some(index) = self @@ -1005,14 +1015,15 @@ impl ProcWidgetState { self.proc_search.search_state.walk_backward(); } - /// Returns the number of columns *enabled*. Note this differs from *visible* - a column may be enabled but not - /// visible (e.g. off screen). + /// Returns the number of columns *enabled*. Note this differs from + /// *visible* - a column may be enabled but not visible (e.g. off + /// screen). pub fn num_enabled_columns(&self) -> usize { self.table.columns.iter().filter(|c| !c.is_hidden).count() } - /// Sets the [`ProcWidgetState`]'s current sort index to whatever was in the sort table if possible, then closes the - /// sort table. + /// Sets the [`ProcWidgetState`]'s current sort index to whatever was in the + /// sort table if possible, then closes the sort table. pub(crate) fn use_sort_table_value(&mut self) { self.table.set_sort_index(self.sort_table.current_index()); @@ -1425,8 +1436,9 @@ mod test { /// Tests toggling if both mem and mem% columns are configured. /// - /// Currently, this test doesn't really do much, since we treat these two columns as the same - this test is - /// intended for use later when we might allow both at the same time. + /// Currently, this test doesn't really do much, since we treat these two + /// columns as the same - this test is intended for use later when we + /// might allow both at the same time. #[test] fn double_memory_sim_toggle() { let init_columns = [ @@ -1461,8 +1473,9 @@ mod test { /// Tests toggling if both pid and count columns are configured. /// - /// Currently, this test doesn't really do much, since we treat these two columns as the same - this test is - /// intended for use later when we might allow both at the same time. + /// Currently, this test doesn't really do much, since we treat these two + /// columns as the same - this test is intended for use later when we + /// might allow both at the same time. #[test] fn pid_and_count_sim_toggle() { let init_columns = [ @@ -1498,8 +1511,9 @@ mod test { /// Tests toggling if both command and name columns are configured. /// - /// Currently, this test doesn't really do much, since we treat these two columns as the same - this test is - /// intended for use later when we might allow both at the same time. + /// Currently, this test doesn't really do much, since we treat these two + /// columns as the same - this test is intended for use later when we + /// might allow both at the same time. #[test] fn command_name_sim_toggle() { let init_columns = [ diff --git a/src/widgets/process_table/proc_widget_data.rs b/src/widgets/process_table/proc_widget_data.rs index 034c36e5..d2eb6b97 100644 --- a/src/widgets/process_table/proc_widget_data.rs +++ b/src/widgets/process_table/proc_widget_data.rs @@ -41,9 +41,9 @@ impl From<&'static str> for Id { } impl Id { - /// Returns the ID as a lowercase [`String`], with no prefix. This is primarily useful for - /// cases like sorting where we treat everything as the same case (e.g. `Discord` comes before - /// `dkms`). + /// Returns the ID as a lowercase [`String`], with no prefix. This is + /// primarily useful for cases like sorting where we treat everything as + /// the same case (e.g. `Discord` comes before `dkms`). pub fn to_lowercase(&self) -> String { match &self.id_type { IdType::Name(name) => name.to_lowercase(), @@ -306,7 +306,8 @@ impl DataToCell<ProcColumn> for ProcWidgetData { let calculated_width = calculated_width.get(); // TODO: Optimize the string allocations here... - // TODO: Also maybe just pull in the to_string call but add a variable for the differences. + // TODO: Also maybe just pull in the to_string call but add a variable for the + // differences. Some(match column { ProcColumn::CpuPercent => format!("{:.1}%", self.cpu_usage_percent).into(), ProcColumn::MemoryVal | ProcColumn::MemoryPercent => self.mem_usage.to_string().into(), diff --git a/tests/integration/arg_tests.rs b/tests/integration/arg_tests.rs index f0f4ec3b..e8804421 100644 --- a/tests/integration/arg_tests.rs +++ b/tests/integration/arg_tests.rs @@ -1,4 +1,5 @@ -//! These tests are mostly here just to ensure that invalid results will be caught when passing arguments. +//! These tests are mostly here just to ensure that invalid results will be +//! caught when passing arguments. use assert_cmd::prelude::*; use predicates::prelude::*; diff --git a/tests/integration/util.rs b/tests/integration/util.rs index ce77bed6..350b7da9 100644 --- a/tests/integration/util.rs +++ b/tests/integration/util.rs @@ -19,13 +19,14 @@ fn get_qemu_target(arch: &str) -> &str { } } -/// This is required since running binary tests via cross can cause be tricky! We need to basically "magically" grab -/// the correct runner in some cases, which can be done by inspecting env variables that should only show up while +/// This is required since running binary tests via cross can cause be tricky! +/// We need to basically "magically" grab the correct runner in some cases, +/// which can be done by inspecting env variables that should only show up while /// using cross. /// /// Originally inspired by [ripgrep's test files](https://cs.github.com/BurntSushi/ripgrep/blob/9f0e88bcb14e02da1b88872435b17d74786640b5/tests/util.rs#L470), -/// but adapted to work more generally with the architectures supported by bottom after looking through cross' -/// [linux-runner](https://github.com/cross-rs/cross/blob/main/docker/linux-runner) file. +/// but adapted to work more generally with the architectures supported by +/// bottom after looking through cross' [linux-runner](https://github.com/cross-rs/cross/blob/main/docker/linux-runner) file. fn cross_runner() -> Option<String> { const TARGET_RUNNER: &str = "CARGO_TARGET_RUNNER"; const CROSS_RUNNER: &str = "CROSS_RUNNER";