From 982b7181a6760bb9b0b6d35171a35249c8a7d1e5 Mon Sep 17 00:00:00 2001 From: Clement Tsang <34804052+ClementTsang@users.noreply.github.com> Date: Sun, 16 Jun 2024 02:15:36 -0400 Subject: [PATCH] change: change how disk, temp, and net filters in config are set (#1481) * change: change how disk, temp, and net filters in config are set * run rustfmt * update default config --- CHANGELOG.md | 5 + Cargo.toml | 1 + sample_configs/default_config.toml | 73 ++++++----- src/app.rs | 57 +++++---- src/app/data_farmer.rs | 15 +-- src/app/filter.rs | 5 +- src/app/frozen_state.rs | 5 +- src/app/process_killer.rs | 6 +- src/app/query.rs | 45 ++++--- src/app/states.rs | 12 +- src/canvas.rs | 17 +-- src/canvas/components/data_table.rs | 19 +-- src/canvas/components/data_table/column.rs | 33 +++-- src/canvas/components/data_table/data_type.rs | 5 +- src/canvas/components/data_table/draw.rs | 3 +- src/canvas/components/data_table/sortable.rs | 44 ++++--- src/canvas/components/time_graph.rs | 16 ++- .../components/tui_widget/pipe_gauge.rs | 7 +- .../components/tui_widget/time_chart.rs | 118 ++++++++++-------- .../tui_widget/time_chart/canvas.rs | 83 ++++++------ .../tui_widget/time_chart/points.rs | 27 ++-- src/canvas/components/widget_carousel.rs | 9 +- src/canvas/dialogs/dd_dialog.rs | 15 +-- src/canvas/dialogs/help_dialog.rs | 4 +- src/canvas/styling.rs | 4 +- src/canvas/widgets/battery_display.rs | 5 +- src/canvas/widgets/cpu_basic.rs | 4 +- src/canvas/widgets/cpu_graph.rs | 3 +- src/canvas/widgets/network_graph.rs | 50 +++++--- src/canvas/widgets/process_table.rs | 9 +- src/constants.rs | 103 +++++++-------- src/data_collection.rs | 27 ++-- src/data_collection/batteries.rs | 3 +- src/data_collection/cpu/sysinfo.rs | 3 +- src/data_collection/disks.rs | 16 +-- src/data_collection/disks/freebsd.rs | 3 +- src/data_collection/disks/unix.rs | 19 +-- .../disks/unix/file_systems.rs | 7 +- .../disks/unix/linux/counters.rs | 6 +- .../disks/unix/linux/partition.rs | 13 +- .../disks/unix/macos/counters.rs | 23 ++-- .../disks/unix/macos/io_kit/bindings.rs | 3 +- .../disks/unix/macos/io_kit/io_iterator.rs | 6 +- .../disks/unix/macos/io_kit/io_object.rs | 15 ++- .../disks/unix/other/bindings.rs | 5 +- .../disks/unix/other/partition.rs | 3 +- src/data_collection/disks/unix/usage.rs | 13 +- src/data_collection/disks/windows/bindings.rs | 13 +- src/data_collection/disks/zfs_io_counters.rs | 3 +- src/data_collection/memory.rs | 3 +- src/data_collection/memory/sysinfo.rs | 11 +- src/data_collection/network/sysinfo.rs | 3 +- src/data_collection/processes.rs | 3 +- src/data_collection/processes/linux.rs | 16 +-- .../processes/linux/process.rs | 47 ++++--- src/data_collection/processes/macos.rs | 3 +- .../processes/macos/sysctl_bindings.rs | 20 +-- .../processes/unix/process_ext.rs | 5 +- .../processes/unix/user_table.rs | 3 +- src/data_collection/processes/windows.rs | 5 +- src/data_collection/temperature.rs | 3 +- src/data_collection/temperature/linux.rs | 73 ++++++----- src/data_collection/temperature/sysinfo.rs | 3 +- src/data_conversion.rs | 35 ++++-- src/main.rs | 40 +++--- src/options.rs | 97 ++++++++------ src/options/args.rs | 7 +- src/options/colours.rs | 7 +- src/options/config.rs | 27 ++-- src/options/config/cpu.rs | 3 +- src/options/config/disk.rs | 13 ++ src/options/config/ignore_list.rs | 3 +- src/options/config/network.rs | 10 ++ .../config/{process_columns.rs => process.rs} | 21 ++-- src/options/config/temperature.rs | 10 ++ src/utils/data_prefixes.rs | 20 +-- src/utils/general.rs | 8 +- src/utils/logging.rs | 17 +-- src/utils/strings.rs | 7 +- src/widgets.rs | 3 +- src/widgets/cpu_graph.rs | 12 +- src/widgets/process_table.rs | 64 ++++++---- src/widgets/process_table/proc_widget_data.rs | 9 +- tests/integration/arg_tests.rs | 3 +- tests/integration/util.rs | 9 +- 85 files changed, 954 insertions(+), 652 deletions(-) create mode 100644 src/options/config/disk.rs create mode 100644 src/options/config/network.rs rename src/options/config/{process_columns.rs => process.rs} (72%) create mode 100644 src/options/config/temperature.rs 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), 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 = None; let mut value: Option = 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>, 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 { // 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> { pub columns: Vec, pub state: DataTableState, @@ -89,8 +91,9 @@ impl, H: ColumnHeader, S: SortType, C: DataTableColumn Option { 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, }, - /// 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 { /// 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 { /// 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 Column { } pub trait CalculateColumnWidths { - /// 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; } 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 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>; /// 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 { /// 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 SortColumn where T: ColumnHeader + SortsRow, { - /// 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) -> 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 { 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>, - /// 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>>, @@ -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(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