feature: Beginnings of in-app config (#231)

Initial refactorings and additions to support in-app config.

- Refactor our current options logic to support in-app configs.  That is, we can write to a config file with our changes now.
- The default action when creating a new config file is to leave it blank. (TBD and for now, not sure on this one)
- Previously, we would set everything in a config file on startup; now we need to read from the config TOML struct whenever.
- `C` keybind is now occupied for configs.
- `no_write` option to never write to a config file.
This commit is contained in:
Clement Tsang 2020-09-22 18:12:36 -04:00 committed by GitHub
parent b0b174eb98
commit 6db76029e2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 474 additions and 396 deletions

View file

@ -7,5 +7,5 @@ echo "Running pre-push hook:"
echo "Executing: cargo +nightly clippy -- -D clippy::all"
cargo +nightly clippy -- -D clippy::all
echo "Executing: cargo test"
cargo test
# echo "Executing: cargo test"
# cargo test

View file

@ -55,6 +55,7 @@
"hjkl",
"htop",
"indexmap",
"keybinds",
"libc",
"markdownlint",
"memb",

View file

@ -19,6 +19,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [#223](https://github.com/ClementTsang/bottom/pull/223): Add tree mode for processes.
- [](): Add in-app configuration.
### Changes
- [#213](https://github.com/ClementTsang/bottom/pull/213), [#214](https://github.com/ClementTsang/bottom/pull/214): Updated help descriptions, added auto-complete generation.

View file

@ -46,7 +46,7 @@ If you want to help contribute by submitting a PR, by all means, I'm open! In re
- You can check clippy using `cargo clippy`.
- I use [cargo-husky](https://github.com/rhysd/cargo-husky) to automatically run a `cargo clippy` and `cargo test` check.
- I use [cargo-husky](https://github.com/rhysd/cargo-husky) to automatically run a `cargo clippy` check.
- You may notice that I have fern and log as dependencies; this is mostly for easy debugging via the `debug!()` macro. It writes to the `debug.log` file that will automatically be created if you run in debug mode (so `cargo run`).

View file

@ -1,4 +1,4 @@
use std::{collections::HashMap, time::Instant};
use std::{collections::HashMap, io::Write, path::PathBuf, time::Instant};
use unicode_segmentation::GraphemeCursor;
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
@ -12,6 +12,7 @@ pub use states::*;
use crate::{
canvas, constants,
options::Config,
utils::error::{BottomError, Result},
Pid,
};
@ -42,6 +43,7 @@ pub struct AppConfigFields {
pub use_old_network_legend: bool,
pub table_gap: u16,
pub disable_click: bool,
pub no_write: bool,
}
/// For filtering out information
@ -100,6 +102,9 @@ pub struct App {
#[builder(default = false, setter(skip))]
pub basic_mode_use_percent: bool,
#[builder(default = false, setter(skip))]
pub is_config_open: bool,
pub cpu_state: CpuState,
pub mem_state: MemState,
pub net_state: NetState,
@ -113,6 +118,8 @@ pub struct App {
pub current_widget: BottomWidget,
pub used_widgets: UsedWidgets,
pub filters: DataFilters,
pub config: Config,
pub config_path: Option<PathBuf>,
}
impl App {
@ -171,6 +178,8 @@ impl App {
}
self.is_force_redraw = true;
} else if self.is_config_open {
self.close_config();
} else {
match self.current_widget.widget_type {
BottomWidgetType::Proc => {
@ -247,10 +256,14 @@ impl App {
self.help_dialog_state.is_showing_help || self.delete_dialog_state.is_showing_dd
}
pub fn on_tab(&mut self) {
// Disallow usage whilst in a dialog and only in processes
fn ignore_normal_keybinds(&self) -> bool {
self.is_config_open || self.is_in_dialog()
}
if !self.is_in_dialog() {
pub fn on_tab(&mut self) {
// Allow usage whilst only in processes
if !self.ignore_normal_keybinds() {
match self.current_widget.widget_type {
BottomWidgetType::Cpu => {
if let Some(cpu_widget_state) = self
@ -319,7 +332,7 @@ impl App {
}
pub fn on_slash(&mut self) {
if !self.is_in_dialog() {
if !self.ignore_normal_keybinds() {
match &self.current_widget.widget_type {
BottomWidgetType::Proc | BottomWidgetType::ProcSort => {
// Toggle on
@ -452,6 +465,8 @@ impl App {
.search_toggle_ignore_case();
proc_widget_state.update_query();
self.proc_state.force_update = Some(self.current_widget.widget_id - 1);
// Also toggle it in the config file.
}
}
}
@ -653,7 +668,8 @@ impl App {
}
pub fn on_up_key(&mut self) {
if !self.is_in_dialog() {
if self.is_config_open {
} else if !self.is_in_dialog() {
self.decrement_position_count();
} else if self.help_dialog_state.is_showing_help {
self.help_scroll_up();
@ -662,7 +678,8 @@ impl App {
}
pub fn on_down_key(&mut self) {
if !self.is_in_dialog() {
if self.is_config_open {
} else if !self.is_in_dialog() {
self.increment_position_count();
} else if self.help_dialog_state.is_showing_help {
self.help_scroll_down();
@ -671,7 +688,8 @@ impl App {
}
pub fn on_left_key(&mut self) {
if !self.is_in_dialog() {
if self.is_config_open {
} else if !self.is_in_dialog() {
match self.current_widget.widget_type {
BottomWidgetType::Proc => {
// if let Some(proc_widget_state) = self
@ -735,7 +753,8 @@ impl App {
}
pub fn on_right_key(&mut self) {
if !self.is_in_dialog() {
if self.is_config_open {
} else if !self.is_in_dialog() {
match self.current_widget.widget_type {
BottomWidgetType::Proc => {
// if let Some(proc_widget_state) = self
@ -804,7 +823,7 @@ impl App {
}
pub fn skip_cursor_beginning(&mut self) {
if !self.is_in_dialog() {
if !self.ignore_normal_keybinds() {
if let BottomWidgetType::ProcSearch = self.current_widget.widget_type {
let is_in_search_widget = self.is_in_search_widget();
if let Some(proc_widget_state) = self
@ -836,11 +855,12 @@ impl App {
}
}
}
} else if self.is_config_open {
}
}
pub fn skip_cursor_end(&mut self) {
if !self.is_in_dialog() {
if !self.ignore_normal_keybinds() {
if let BottomWidgetType::ProcSearch = self.current_widget.widget_type {
let is_in_search_widget = self.is_in_search_widget();
if let Some(proc_widget_state) = self
@ -882,6 +902,7 @@ impl App {
}
}
}
} else if self.is_config_open {
}
}
@ -945,7 +966,7 @@ impl App {
}
// Forbid any char key presses when showing a dialog box...
if !self.is_in_dialog() {
if !self.ignore_normal_keybinds() {
let current_key_press_inst = Instant::now();
if current_key_press_inst
.duration_since(self.last_key_press)
@ -1034,6 +1055,7 @@ impl App {
'k' | 'l' => self.on_right_key(),
_ => {}
}
} else if self.is_config_open {
}
}
@ -1086,6 +1108,9 @@ impl App {
self.data_collection.set_frozen_time();
}
}
'C' => {
// self.open_config(),
}
'c' => {
if let BottomWidgetType::Proc = self.current_widget.widget_type {
if let Some(proc_widget_state) = self
@ -1227,6 +1252,7 @@ impl App {
's' => self.toggle_sort(),
'I' => self.invert_sort(),
'%' => self.toggle_percentages(),
' ' => self.on_space(),
_ => {}
}
@ -1237,6 +1263,38 @@ impl App {
}
}
pub fn on_space(&mut self) {}
pub fn open_config(&mut self) {
self.is_config_open = true;
self.is_force_redraw = true;
}
pub fn close_config(&mut self) {
self.is_config_open = false;
self.is_force_redraw = true;
}
/// Call this whenever the config value is updated!
fn update_config_file(&mut self) -> anyhow::Result<()> {
if self.app_config_fields.no_write {
// Don't write!
// FIXME: [CONFIG] This should be made VERY clear to the user... make a thing saying "it will not write due to no_write option"
Ok(())
} else if let Some(config_path) = &self.config_path {
// Update
std::fs::File::open(config_path)?
.write_all(toml::to_string(&self.config)?.as_bytes())?;
Ok(())
} else {
// FIXME: [CONFIG] Put an actual error message?
Err(anyhow::anyhow!(
"Config path was missing, please try restarting bottom..."
))
}
}
pub fn kill_highlighted_process(&mut self) -> Result<()> {
if let BottomWidgetType::Proc = self.current_widget.widget_type {
if let Some(current_selected_processes) = &self.to_delete_process_list {
@ -1268,7 +1326,8 @@ impl App {
}
fn expand_widget(&mut self) {
if !self.is_in_dialog() && !self.app_config_fields.use_basic_mode {
// TODO: [BASIC] Expansion in basic mode.
if !self.ignore_normal_keybinds() && !self.app_config_fields.use_basic_mode {
// Pop-out mode. We ignore if in process search.
match self.current_widget.widget_type {
@ -1300,7 +1359,7 @@ impl App {
- Reflection direction.
*/
if !self.is_in_dialog() && !self.is_expanded {
if !self.ignore_normal_keybinds() && !self.is_expanded {
if let Some(new_widget_id) = &(match direction {
WidgetDirection::Left => self.current_widget.left_neighbour,
WidgetDirection::Right => self.current_widget.right_neighbour,
@ -1731,7 +1790,7 @@ impl App {
}
pub fn skip_to_first(&mut self) {
if !self.is_in_dialog() {
if !self.ignore_normal_keybinds() {
match self.current_widget.widget_type {
BottomWidgetType::Proc => {
if let Some(proc_widget_state) = self
@ -1782,13 +1841,14 @@ impl App {
_ => {}
}
self.reset_multi_tap_keys();
} else {
} else if self.is_config_open {
} else if self.help_dialog_state.is_showing_help {
self.help_dialog_state.scroll_state.current_scroll_index = 0;
}
}
pub fn skip_to_last(&mut self) {
if !self.is_in_dialog() {
if !self.ignore_normal_keybinds() {
match self.current_widget.widget_type {
BottomWidgetType::Proc => {
if let Some(proc_widget_state) = self
@ -1858,7 +1918,8 @@ impl App {
_ => {}
}
self.reset_multi_tap_keys();
} else {
} else if self.is_config_open {
} else if self.help_dialog_state.is_showing_help {
self.help_dialog_state.scroll_state.current_scroll_index = self
.help_dialog_state
.scroll_state
@ -1868,7 +1929,7 @@ impl App {
}
pub fn decrement_position_count(&mut self) {
if !self.is_in_dialog() {
if !self.ignore_normal_keybinds() {
match self.current_widget.widget_type {
BottomWidgetType::Proc => self.increment_process_position(-1),
BottomWidgetType::ProcSort => self.increment_process_sort_position(-1),
@ -1881,7 +1942,7 @@ impl App {
}
pub fn increment_position_count(&mut self) {
if !self.is_in_dialog() {
if !self.ignore_normal_keybinds() {
match self.current_widget.widget_type {
BottomWidgetType::Proc => self.increment_process_position(1),
BottomWidgetType::ProcSort => self.increment_process_sort_position(1),

View file

@ -187,7 +187,11 @@ impl ProcessQuery for ProcWidgetState {
let initial_or = Or {
lhs: And {
lhs: Prefix {
or: Some(Box::new(list_of_ors.pop_front().unwrap())),
or: if let Some(or) = list_of_ors.pop_front() {
Some(Box::new(or))
} else {
None
},
compare_prefix: None,
regex_prefix: None,
},

View file

@ -280,6 +280,7 @@ impl Default for ProcColumn {
}
}
// TODO: [SORTING] Sort by clicking on column header (ie: click on cpu, sort/invert cpu sort)?
impl ProcColumn {
/// Returns its new status.
pub fn toggle(&mut self, column: &ProcessSorting) -> Option<bool> {
@ -303,8 +304,12 @@ impl ProcColumn {
self.ordered_columns
.iter()
.filter_map(|column_type| {
if self.column_mapping.get(&column_type).unwrap().enabled {
Some(1)
if let Some(col_map) = self.column_mapping.get(&column_type) {
if col_map.enabled {
Some(1)
} else {
None
}
} else {
None
}
@ -467,16 +472,20 @@ impl ProcWidgetState {
}
pub fn toggle_command_and_name(&mut self, is_using_command: bool) {
self.columns
if let Some(pn) = self
.columns
.column_mapping
.get_mut(&ProcessSorting::ProcessName)
.unwrap()
.enabled = !is_using_command;
self.columns
{
pn.enabled = !is_using_command;
}
if let Some(c) = self
.columns
.column_mapping
.get_mut(&ProcessSorting::Command)
.unwrap()
.enabled = is_using_command;
{
c.enabled = is_using_command;
}
}
pub fn get_cursor_position(&self) -> usize {
@ -798,3 +807,19 @@ pub struct ParagraphScrollState {
pub current_scroll_index: u16,
pub max_scroll_index: u16,
}
#[derive(Default)]
pub struct ConfigState {
pub current_category_index: usize,
pub category_list: Vec<ConfigCategory>,
}
#[derive(Default)]
pub struct ConfigCategory {
pub category_name: &'static str,
pub options_list: Vec<ConfigOption>,
}
pub struct ConfigOption {
pub set_function: Box<dyn Fn() -> anyhow::Result<()>>,
}

View file

@ -32,7 +32,7 @@ fn main() -> Result<()> {
}
let matches = clap::get_matches();
let config_path = read_config(matches.value_of("CONFIG_LOCATION"))
let config_path = read_config(matches.value_of("config_location"))
.context("Unable to access the given config file location.")?;
let config: Config = create_or_get_config(&config_path)
.context("Unable to properly parse or create the config file.")?;
@ -49,6 +49,7 @@ fn main() -> Result<()> {
&widget_layout,
default_widget_id,
&default_widget_type_option,
config_path,
)?;
// Create painter and set colours.
@ -56,10 +57,8 @@ fn main() -> Result<()> {
widget_layout,
app.app_config_fields.table_gap,
app.app_config_fields.use_basic_mode,
);
generate_config_colours(&config, &mut painter)?;
painter.colours.generate_remaining_cpu_colours();
painter.complete_painter_init();
&config,
)?;
// Set up input handling
let (sender, receiver) = mpsc::channel();
@ -80,7 +79,7 @@ fn main() -> Result<()> {
// Event loop
let (reset_sender, reset_receiver) = mpsc::channel();
create_event_thread(
create_collection_thread(
sender,
reset_receiver,
&app.app_config_fields,
@ -105,8 +104,7 @@ fn main() -> Result<()> {
ctrlc::set_handler(move || {
ist_clone.store(true, Ordering::SeqCst);
termination_hook();
})
.unwrap();
})?;
let mut first_run = true;
while !is_terminated.load(Ordering::SeqCst) {

View file

@ -1,3 +1,4 @@
use anyhow::Context;
use itertools::izip;
use std::collections::HashMap;
@ -10,6 +11,7 @@ use tui::{
use canvas_colours::*;
use dialogs::*;
use screens::*;
use widgets::*;
use crate::{
@ -20,12 +22,14 @@ use crate::{
},
constants::*,
data_conversion::{ConvertedBatteryData, ConvertedCpuData, ConvertedProcessData},
options::Config,
utils::error,
};
mod canvas_colours;
mod dialogs;
mod drawing_utils;
mod screens;
mod widgets;
/// Point is of time, data
@ -65,13 +69,15 @@ pub struct Painter {
col_constraints: Vec<Vec<Constraint>>,
col_row_constraints: Vec<Vec<Vec<Constraint>>>,
layout_constraints: Vec<Vec<Vec<Vec<Constraint>>>>,
widget_layout: BottomLayout,
derived_widget_draw_locs: Vec<Vec<Vec<Vec<Rect>>>>,
widget_layout: BottomLayout,
table_height_offset: u16,
}
impl Painter {
pub fn init(widget_layout: BottomLayout, table_gap: u16, is_basic_mode: bool) -> Self {
pub fn init(
widget_layout: BottomLayout, table_gap: u16, is_basic_mode: bool, config: &Config,
) -> anyhow::Result<Self> {
// Now for modularity; we have to also initialize the base layouts!
// We want to do this ONCE and reuse; after this we can just construct
// based on the console size.
@ -139,7 +145,7 @@ impl Painter {
col_constraints.push(new_col_constraints);
});
Painter {
let mut painter = Painter {
colours: CanvasColours::default(),
height: 0,
width: 0,
@ -152,12 +158,128 @@ impl Painter {
widget_layout,
derived_widget_draw_locs: Vec::default(),
table_height_offset: if is_basic_mode { 2 } else { 4 } + table_gap,
};
painter.generate_config_colours(config)?;
painter.colours.generate_remaining_cpu_colours();
painter.complete_painter_init();
Ok(painter)
}
pub fn generate_config_colours(&mut self, config: &Config) -> anyhow::Result<()> {
if let Some(colours) = &config.colors {
if let Some(border_color) = &colours.border_color {
self.colours
.set_border_colour(border_color)
.context("Update 'border_color' in your config file..")?;
}
if let Some(highlighted_border_color) = &colours.highlighted_border_color {
self.colours
.set_highlighted_border_colour(highlighted_border_color)
.context("Update 'highlighted_border_color' in your config file..")?;
}
if let Some(text_color) = &colours.text_color {
self.colours
.set_text_colour(text_color)
.context("Update 'text_color' in your config file..")?;
}
if let Some(avg_cpu_color) = &colours.avg_cpu_color {
self.colours
.set_avg_cpu_colour(avg_cpu_color)
.context("Update 'avg_cpu_color' in your config file..")?;
}
if let Some(all_cpu_color) = &colours.all_cpu_color {
self.colours
.set_all_cpu_colour(all_cpu_color)
.context("Update 'all_cpu_color' in your config file..")?;
}
if let Some(cpu_core_colors) = &colours.cpu_core_colors {
self.colours
.set_cpu_colours(cpu_core_colors)
.context("Update 'cpu_core_colors' in your config file..")?;
}
if let Some(ram_color) = &colours.ram_color {
self.colours
.set_ram_colour(ram_color)
.context("Update 'ram_color' in your config file..")?;
}
if let Some(swap_color) = &colours.swap_color {
self.colours
.set_swap_colour(swap_color)
.context("Update 'swap_color' in your config file..")?;
}
if let Some(rx_color) = &colours.rx_color {
self.colours
.set_rx_colour(rx_color)
.context("Update 'rx_color' in your config file..")?;
}
if let Some(tx_color) = &colours.tx_color {
self.colours
.set_tx_colour(tx_color)
.context("Update 'tx_color' in your config file..")?;
}
// if let Some(rx_total_color) = &colours.rx_total_color {
// painter.colours.set_rx_total_colour(rx_total_color)?;
// }
// if let Some(tx_total_color) = &colours.tx_total_color {
// painter.colours.set_tx_total_colour(tx_total_color)?;
// }
if let Some(table_header_color) = &colours.table_header_color {
self.colours
.set_table_header_colour(table_header_color)
.context("Update 'table_header_color' in your config file..")?;
}
if let Some(scroll_entry_text_color) = &colours.selected_text_color {
self.colours
.set_scroll_entry_text_color(scroll_entry_text_color)
.context("Update 'selected_text_color' in your config file..")?;
}
if let Some(scroll_entry_bg_color) = &colours.selected_bg_color {
self.colours
.set_scroll_entry_bg_color(scroll_entry_bg_color)
.context("Update 'selected_bg_color' in your config file..")?;
}
if let Some(widget_title_color) = &colours.widget_title_color {
self.colours
.set_widget_title_colour(widget_title_color)
.context("Update 'widget_title_color' in your config file..")?;
}
if let Some(graph_color) = &colours.graph_color {
self.colours
.set_graph_colour(graph_color)
.context("Update 'graph_color' in your config file..")?;
}
if let Some(battery_colors) = &colours.battery_colors {
self.colours
.set_battery_colors(battery_colors)
.context("Update 'battery_colors' in your config file.")?;
}
}
Ok(())
}
/// Must be run once before drawing, but after setting colours.
/// This is to set some remaining styles and text.
pub fn complete_painter_init(&mut self) {
fn complete_painter_init(&mut self) {
self.is_mac_os = cfg!(target_os = "macos");
let mut styled_help_spans = Vec::new();
@ -191,6 +313,9 @@ impl Painter {
self.styled_help_text = styled_help_spans;
}
// FIXME: [CONFIG] write this, should call painter init and any changed colour functions...
pub fn update_painter_colours(&mut self) {}
pub fn draw_data<B: Backend>(
&mut self, terminal: &mut Terminal<B>, app_state: &mut app::App,
) -> error::Result<()> {
@ -406,6 +531,13 @@ impl Painter {
),
_ => {}
}
} else if app_state.is_config_open {
let rect = Layout::default()
.margin(0)
.constraints([Constraint::Percentage(100)].as_ref())
.split(f.size())[0];
self.draw_config_screen(&mut f, app_state, rect)
} else if app_state.app_config_fields.use_basic_mode {
// Basic mode. This basically removes all graphs but otherwise
// the same info.

View file

@ -1,5 +1,6 @@
use unicode_width::UnicodeWidthStr;
use crate::{app::App, canvas::Painter, constants};
use tui::{
backend::Backend,
layout::{Alignment, Rect},
@ -7,8 +8,6 @@ use tui::{
widgets::{Block, Borders, Paragraph},
};
use crate::{app::App, canvas::Painter, constants};
const HELP_BASE: &str = " Help ── Esc to close ";
pub trait HelpDialog {
@ -17,6 +16,7 @@ pub trait HelpDialog {
);
}
// TODO: [REFACTOR] Make generic dialog boxes to build off of instead?
impl HelpDialog for Painter {
fn draw_help_dialog<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect,

3
src/canvas/screens.rs Normal file
View file

@ -0,0 +1,3 @@
pub mod config_screen;
pub use config_screen::*;

View file

@ -0,0 +1,57 @@
use crate::{app::App, canvas::Painter, constants};
use tui::{
backend::Backend,
layout::Constraint,
layout::Direction,
layout::Layout,
layout::{Alignment, Rect},
terminal::Frame,
widgets::{Block, Borders, Paragraph},
};
pub trait ConfigScreen {
fn draw_config_screen<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect,
);
}
impl ConfigScreen for Painter {
fn draw_config_screen<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect,
) {
let config_block = Block::default()
.title(&" Config ")
.title_style(self.colours.border_style)
.style(self.colours.border_style)
.borders(Borders::ALL)
.border_style(self.colours.border_style);
f.render_widget(config_block, draw_loc);
// let margined_draw_locs = Layout::default()
// .margin(2)
// .direction(Direction::Horizontal)
// .constraints(
// [
// Constraint::Percentage(33),
// Constraint::Percentage(34),
// Constraint::Percentage(33),
// ]
// .as_ref(),
// )
// .split(draw_loc)
// .into_iter()
// .map(|loc| {
// // Required to properly margin in *between* the rectangles.
// Layout::default()
// .horizontal_margin(1)
// .constraints([Constraint::Percentage(100)].as_ref())
// .split(loc)[0]
// })
// .collect::<Vec<Rect>>();
// for dl in margined_draw_locs {
// f.render_widget(Block::default().borders(Borders::ALL), dl);
// }
}
}

View file

@ -223,7 +223,7 @@ impl BatteryDisplayWidget for Painter {
.block(Block::default())
.divider(tui::symbols::line::VERTICAL)
.style(self.colours.text_style)
.highlight_style(self.colours.currently_selected_text_style)
.highlight_style(self.colours.currently_selected_text_style) //FIXME: [HIGHLIGHT] THIS IS BROKEN ON TUI's SIDE, override this with your own style...
.select(battery_widget_state.currently_selected_battery_index),
tab_draw_loc,
);

View file

@ -78,6 +78,7 @@ impl MemGraphWidget for Painter {
.graph_type(tui::widgets::GraphType::Line),
);
// FIXME: [SWAP] Hide this if denominator is 0...
let swap_label = format!(
"SWP:{}{}",
app_state.canvas_data.swap_label_percent, app_state.canvas_data.swap_label_frac

View file

@ -413,7 +413,7 @@ impl ProcessTableWidget for Painter {
}
});
// TODO: gotop's "x out of y" thing is really nice to help keep track of the scroll position.
// TODO: gotop's "x out of y" thing is really nice to help keep track of the scroll position. Add to everything?
f.render_stateful_widget(
Table::new(process_headers.iter(), process_rows)
.block(process_block)
@ -531,6 +531,8 @@ impl ProcessTableWidget for Painter {
let query = proc_widget_state.get_current_search_query().as_str();
let grapheme_indices = UnicodeSegmentation::grapheme_indices(query, true);
// TODO: [CURSOR] blank cursor if not selected
// TODO: [CURSOR] blinking cursor?
let query_with_cursor = build_query(
is_on_widget,
grapheme_indices,
@ -579,6 +581,8 @@ impl ProcessTableWidget for Painter {
self.colours.text_style
};
// FIXME: [MOUSE] Mouse support for these in search
// FIXME: [MOVEMENT] Movement support for these in search
let option_text = vec![
Text::raw("\n"),
Text::styled(

View file

@ -20,7 +20,7 @@ pub fn get_matches() -> clap::ArgMatches<'static> {
pub fn build_app() -> App<'static, 'static> {
// Temps
let kelvin = Arg::with_name("KELVIN")
let kelvin = Arg::with_name("kelvin")
.short("k")
.long("kelvin")
.help("Sets the temperature type to Kelvin.")
@ -28,7 +28,7 @@ pub fn build_app() -> App<'static, 'static> {
"\
Sets the temperature type to Kelvin.\n\n",
);
let fahrenheit = Arg::with_name("FAHRENHEIT")
let fahrenheit = Arg::with_name("fahrenheit")
.short("f")
.long("fahrenheit")
.help("Sets the temperature type to Fahrenheit.")
@ -36,7 +36,7 @@ Sets the temperature type to Kelvin.\n\n",
"\
Sets the temperature type to Fahrenheit.\n\n",
);
let celsius = Arg::with_name("CELSIUS")
let celsius = Arg::with_name("celsius")
.short("c")
.long("celsius")
.help("Sets the temperature type to Celsius.")
@ -47,7 +47,7 @@ option.\n\n",
);
// All flags. These are in alphabetical order
let autohide_time = Arg::with_name("AUTOHIDE_TIME")
let autohide_time = Arg::with_name("autohide_time")
.long("autohide_time")
.help("Temporarily shows the time scale in graphs.")
.long_help(
@ -56,7 +56,7 @@ Automatically hides the time scaling in graphs after being
shown for a brief moment when zoomed in/out. If time is
disabled via --hide_time then this will have no effect.\n\n\n",
);
let basic = Arg::with_name("BASIC_MODE")
let basic = Arg::with_name("basic")
.short("b")
.long("basic")
.help("Hides graphs and uses a more basic look.")
@ -65,7 +65,7 @@ disabled via --hide_time then this will have no effect.\n\n\n",
Hides graphs and uses a more basic look. Design is largely
inspired by htop's.\n\n",
);
let battery = Arg::with_name("BATTERY")
let battery = Arg::with_name("battery")
.long("battery")
.help("Shows the battery widget.")
.long_help(
@ -73,7 +73,7 @@ inspired by htop's.\n\n",
Shows the battery widget in default or basic mode. No effect on
custom layouts.\n\n",
);
let case_sensitive = Arg::with_name("CASE_SENSITIVE")
let case_sensitive = Arg::with_name("case_sensitive")
.short("S")
.long("case_sensitive")
.help("Enables case sensitivity by default.")
@ -81,14 +81,14 @@ custom layouts.\n\n",
"\
When searching for a process, enables case sensitivity by default.\n\n",
);
let disable_click = Arg::with_name("DISABLE_CLICK")
let disable_click = Arg::with_name("disable_click")
.long("disable_click")
.help("Disables mouse clicks.")
.long_help(
"\
Disables mouse clicks from interacting with the program.\n\n",
);
let dot_marker = Arg::with_name("DOT_MARKER")
let dot_marker = Arg::with_name("dot_marker")
.short("m")
.long("dot_marker")
.help("Uses a dot marker for graphs.")
@ -97,7 +97,7 @@ Disables mouse clicks from interacting with the program.\n\n",
Uses a dot marker for graphs as opposed to the default braille
marker.\n\n",
);
let group = Arg::with_name("GROUP_PROCESSES")
let group = Arg::with_name("group")
.short("g")
.long("group")
.help("Groups processes with the same name by default.")
@ -105,7 +105,7 @@ marker.\n\n",
"\
Groups processes with the same name by default.\n\n",
);
let hide_avg_cpu = Arg::with_name("HIDE_AVG_CPU")
let hide_avg_cpu = Arg::with_name("hide_avg_cpu")
.short("a")
.long("hide_avg_cpu")
.help("Hides the average CPU usage.")
@ -113,21 +113,21 @@ Groups processes with the same name by default.\n\n",
"\
Hides the average CPU usage from being shown.\n\n",
);
let hide_table_gap = Arg::with_name("HIDE_TABLE_GAP")
let hide_table_gap = Arg::with_name("hide_table_gap")
.long("hide_table_gap")
.help("Hides the spacing between table headers and entries.")
.long_help(
"\
Hides the spacing between table headers and entries.\n\n",
);
let hide_time = Arg::with_name("HIDE_TIME")
let hide_time = Arg::with_name("hide_time")
.long("hide_time")
.help("Completely hides the time scaling.")
.long_help(
"\
Completely hides the time scaling from being shown.\n\n",
);
let left_legend = Arg::with_name("LEFT_LEGEND")
let left_legend = Arg::with_name("left_legend")
.short("l")
.long("left_legend")
.help("Puts the CPU chart legend to the left side.")
@ -135,7 +135,14 @@ Completely hides the time scaling from being shown.\n\n",
"\
Puts the CPU chart legend to the left side rather than the right side.\n\n",
);
let regex = Arg::with_name("REGEX_DEFAULT")
let no_write = Arg::with_name("no_write")
.long("no_write")
.help("Disables writing to the config file.")
.long_help(
"\
Disables config changes in-app from writing to the config file.",
);
let regex = Arg::with_name("regex")
.short("R")
.long("regex")
.help("Enables regex by default.")
@ -143,7 +150,7 @@ Puts the CPU chart legend to the left side rather than the right side.\n\n",
"\
When searching for a process, enables regex by default.\n\n",
);
let current_usage = Arg::with_name("USE_CURR_USAGE")
let current_usage = Arg::with_name("current_usage")
.short("u")
.long("current_usage")
.help("Sets process CPU% to be based on current CPU%.")
@ -152,7 +159,7 @@ When searching for a process, enables regex by default.\n\n",
Sets process CPU% usage to be based on the current system CPU% usage
rather than total CPU usage.\n\n",
);
let use_old_network_legend = Arg::with_name("USE_OLD_NETWORK_LEGEND")
let use_old_network_legend = Arg::with_name("use_old_network_legend")
.long("use_old_network_legend")
.help("DEPRECATED - uses the older network legend.")
.long_help(
@ -160,7 +167,7 @@ rather than total CPU usage.\n\n",
DEPRECATED - uses the older (pre-0.4) network widget legend.
This display is not tested anymore and could be broken.\n\n\n",
);
let whole_word = Arg::with_name("WHOLE_WORD")
let whole_word = Arg::with_name("whole_word")
.short("W")
.long("whole_word")
.help("Enables whole-word matching by default.")
@ -171,7 +178,7 @@ entire query by default.\n\n",
);
// All options. Again, alphabetical order.
let config = Arg::with_name("CONFIG_LOCATION")
let config_location = Arg::with_name("config_location")
.short("C")
.long("config")
.takes_value(true)
@ -182,7 +189,7 @@ entire query by default.\n\n",
Sets the location of the config file. Expects a config
file in the TOML format. If it doesn't exist, one is created.\n\n\n",
);
let default_time_value = Arg::with_name("DEFAULT_TIME_VALUE")
let default_time_value = Arg::with_name("default_time_value")
.short("t")
.long("default_time_value")
.takes_value(true)
@ -193,10 +200,10 @@ file in the TOML format. If it doesn't exist, one is created.\n\n\n",
Default time value for graphs in milliseconds. The minimum
time is 30s (30000), and the default is 60s (60000).\n\n\n",
);
let default_widget_count = Arg::with_name("DEFAULT_WIDGET_COUNT")
let default_widget_count = Arg::with_name("default_widget_count")
.long("default_widget_count")
.takes_value(true)
.requires_all(&["DEFAULT_WIDGET_TYPE"])
.requires_all(&["default_widget_type"])
.value_name("INT")
.help("Sets the n'th selected widget type as the default.")
.long_help(
@ -218,7 +225,7 @@ the default widget. If we set '--default_widget_count 3', it would
use CPU (3) as the default instead.
\n\n",
);
let default_widget_type = Arg::with_name("DEFAULT_WIDGET_TYPE")
let default_widget_type = Arg::with_name("default_widget_type")
.long("default_widget_type")
.takes_value(true)
.value_name("WIDGET TYPE")
@ -257,7 +264,7 @@ Supported widget names:
+--------------------------+
\n\n",
);
let rate = Arg::with_name("RATE_MILLIS")
let rate = Arg::with_name("rate")
.short("r")
.long("rate")
.takes_value(true)
@ -268,7 +275,7 @@ Supported widget names:
Sets a refresh rate in milliseconds. The minimum is 250ms,
and defaults to 1000ms. Smaller values may take more resources.\n\n\n",
);
let time_delta = Arg::with_name("TIME_DELTA")
let time_delta = Arg::with_name("time_delta")
.short("d")
.long("time_delta")
.takes_value(true)
@ -292,12 +299,12 @@ The minimum is 1s (1000), and defaults to 15s (15000).\n\n\n",
.arg(kelvin)
.arg(fahrenheit)
.arg(celsius)
.group(ArgGroup::with_name("TEMPERATURE_TYPE").args(&["KELVIN", "FAHRENHEIT", "CELSIUS"]))
.group(ArgGroup::with_name("TEMPERATURE_TYPE").args(&["kelvin", "fahrenheit", "celsius"]))
.arg(autohide_time)
.arg(basic)
.arg(battery)
.arg(case_sensitive)
.arg(config)
.arg(config_location)
.arg(default_time_value)
.arg(default_widget_count)
.arg(default_widget_type)
@ -308,6 +315,7 @@ The minimum is 1s (1000), and defaults to 15s (15000).\n\n\n",
.arg(hide_table_gap)
.arg(hide_time)
.arg(left_legend)
.arg(no_write)
.arg(rate)
.arg(regex)
.arg(time_delta)

View file

@ -245,155 +245,33 @@ pub const DEFAULT_BATTERY_LAYOUT: &str = r##"
// Config and flags
pub const DEFAULT_CONFIG_FILE_PATH: &str = "bottom/bottom.toml";
// Default config file
// FIXME [CHORE]: Update the default config
pub const DEFAULT_CONFIG_CONTENT: &str = r##"
# This is a default config file for bottom. All of the settings are commented
# out by default; if you wish to change them uncomment and modify as you see
# fit.
pub const CONFIG_TOP_HEAD: &str = r##"# This is bottom's config file. Values in this config file will change when changed in the
# interface. You can also manually change these values.
# This group of options represents a command-line flag/option. Flags explicitly
# added when running (ie: btm -a) will override this config file if an option
# is also set here.
[flags]
"##;
# Whether to hide the average cpu entry.
#hide_avg_cpu = false
pub const CONFIG_DISPLAY_OPTIONS_HEAD: &str = r##"
# These options represent settings that affect how bottom functions.
# If a setting here corresponds to command-line flag, then the flag will temporarily override
# the setting.
"##;
# Whether to use dot markers rather than braille.
#dot_marker = false
pub const CONFIG_COLOUR_HEAD: &str = r##"
# These options represent colour values for various parts of bottom. Note that colour support
# will ultimately depend on the terminal - for example, the Terminal for macOS does NOT like
# custom colours and it may glitch out.
"##;
# The update rate of the application.
#rate = 1000
# Whether to put the CPU legend to the left.
#left_legend = false
# Whether to set CPU% on a process to be based on the total CPU or just current usage.
#current_usage = false
# Whether to group processes with the same name together by default.
#group_processes = false
# Whether to make process searching case sensitive by default.
#case_sensitive = false
# Whether to make process searching look for matching the entire word by default.
#whole_word = false
# Whether to make process searching use regex by default.
#regex = false
# Defaults to Celsius. Temperature is one of:
#temperature_type = "k"
#temperature_type = "f"
#temperature_type = "c"
#temperature_type = "kelvin"
#temperature_type = "fahrenheit"
#temperature_type = "celsius"
# The default time interval (in milliseconds).
#default_time_value = 60000
# The time delta on each zoom in/out action (in milliseconds).
#time_delta = 15000
# Override layout default widget
#default_widget_type = "proc"
#default_widget_count = 1
# Use basic mode
#basic = false
# Use the old network legend style
#use_old_network_legend = false
# Remove space in tables
#hide_table_gap = false
##########################################################
# These are all the components that support custom theming. Note that colour support
# will, at the end of the day, depend on terminal support - for example, the
# macOS default Terminal does NOT like custom colours and it will glitch out.
[colors]
# Represents the colour of table headers (processes, CPU, disks, temperature).
#table_header_color="LightBlue"
# Represents the colour of the label each widget has.
#widget_title_color="Gray"
# Represents the average CPU color.
#avg_cpu_color="Red"
# Represents the colour the core will use in the CPU legend and graph.
#cpu_core_colors=["LightMagenta", "LightYellow", "LightCyan", "LightGreen", "LightBlue", "LightRed", "Cyan", "Green", "Blue", "Red"]
# Represents the colour RAM will use in the memory legend and graph.
#ram_color="LightMagenta"
# Represents the colour SWAP will use in the memory legend and graph.
#swap_color="LightYellow"
# Represents the colour rx will use in the network legend and graph.
#rx_color="LightCyan"
# Represents the colour tx will use in the network legend and graph.
#tx_color="LightGreen"
# Represents the colour of the border of unselected widgets.
#border_color="Gray"
# Represents the colour of the border of selected widgets.
#highlighted_border_color="LightBlue"
# Represents the colour of most text.
#text_color="Gray"
# Represents the colour of text that is selected.
#selected_text_color="Black"
# Represents the background colour of text that is selected.
#selected_bg_color="LightBlue"
# Represents the colour of the lines and text of the graph.
#graph_color="Gray"
# Represents the colours of the battery based on charge
#battery_colors = ["red", "yellow", "yellow", "green", "green", "green"]
##########################################################
# Layout - layouts follow a pattern like this:
pub const CONFIG_LAYOUT_HEAD: &str = r##"
# These options represent how bottom will lay out its widgets. Layouts follow a pattern like this:
# [[row]] represents a row in the application.
# [[row.child]] represents either a widget or a column.
# [[row.child.child]] represents a widget.
#
# All widgets must have the type value set to one of ["cpu", "mem", "proc", "net", "temp", "disk", "empty"].
# All widgets must have the valid type value set to one of ["cpu", "mem", "proc", "net", "temp", "disk", "empty"].
# All layout components have a ratio value - if this is not set, then it defaults to 1.
# The default widget layout:
#[[row]]
# ratio=30
# [[row.child]]
# type="cpu"
#[[row]]
# ratio=40
# [[row.child]]
# ratio=4
# type="mem"
# [[row.child]]
# ratio=3
# [[row.child.child]]
# type="temp"
# [[row.child.child]]
# type="disk"
#[[row]]
# ratio=30
# [[row.child]]
# type="net"
# [[row.child]]
# type="proc"
# default=true
"##;
pub const CONFIG_DIVIDER: &str = r##"
#########################################################################
"##;

View file

@ -20,8 +20,6 @@ use crossterm::{
terminal::{disable_raw_mode, LeaveAlternateScreen},
};
use anyhow::Context;
use app::{
data_harvester::{self, processes::ProcessSorting},
layout_manager::{UsedWidgets, WidgetDirection},
@ -33,20 +31,17 @@ use options::*;
use utils::error;
pub mod app;
pub mod utils {
pub mod error;
pub mod gen_util;
pub mod logging;
}
pub mod canvas;
pub mod clap;
pub mod constants;
pub mod data_conversion;
pub mod options;
pub mod clap;
#[cfg(target_family = "windows")]
pub type Pid = usize;
@ -60,8 +55,11 @@ pub enum BottomEvent<I, J> {
Clean,
}
pub enum ResetEvent {
pub enum CollectionThreadEvent {
Reset,
UpdateConfig(Box<app::AppConfigFields>),
UpdateUsedWidgets(Box<UsedWidgets>),
UpdateUpdateTime(u64),
}
pub fn handle_mouse_event(event: MouseEvent, app: &mut App) {
@ -87,7 +85,7 @@ pub fn handle_mouse_event(event: MouseEvent, app: &mut App) {
}
pub fn handle_key_event_or_break(
event: KeyEvent, app: &mut App, reset_sender: &std::sync::mpsc::Sender<ResetEvent>,
event: KeyEvent, app: &mut App, reset_sender: &std::sync::mpsc::Sender<CollectionThreadEvent>,
) -> bool {
// debug!("KeyEvent: {:?}", event);
@ -144,7 +142,7 @@ pub fn handle_key_event_or_break(
KeyCode::Up => app.move_widget_selection(&WidgetDirection::Up),
KeyCode::Down => app.move_widget_selection(&WidgetDirection::Down),
KeyCode::Char('r') => {
if reset_sender.send(ResetEvent::Reset).is_ok() {
if reset_sender.send(CollectionThreadEvent::Reset).is_ok() {
app.reset();
}
}
@ -213,17 +211,19 @@ pub fn read_config(config_location: Option<&str>) -> error::Result<Option<PathBu
pub fn create_or_get_config(config_path: &Option<PathBuf>) -> error::Result<Config> {
if let Some(path) = config_path {
if let Ok(config_string) = fs::read_to_string(path) {
// We found a config file!
Ok(toml::from_str(config_string.as_str())?)
} else {
// Config file DNE...
if let Some(parent_path) = path.parent() {
fs::create_dir_all(parent_path)?;
}
fs::File::create(path)?.write_all(DEFAULT_CONFIG_CONTENT.as_bytes())?;
Ok(toml::from_str(DEFAULT_CONFIG_CONTENT)?)
fs::File::create(path)?.write_all(CONFIG_TOP_HEAD.as_bytes())?;
Ok(Config::default())
}
} else {
// Don't write otherwise...
Ok(toml::from_str(DEFAULT_CONFIG_CONTENT)?)
// Don't write, the config path was somehow None...
Ok(Config::default())
}
}
@ -239,134 +239,6 @@ pub fn try_drawing(
Ok(())
}
pub fn generate_config_colours(
config: &Config, painter: &mut canvas::Painter,
) -> anyhow::Result<()> {
if let Some(colours) = &config.colors {
if let Some(border_color) = &colours.border_color {
painter
.colours
.set_border_colour(border_color)
.context("Update 'border_color' in your config file..")?;
}
if let Some(highlighted_border_color) = &colours.highlighted_border_color {
painter
.colours
.set_highlighted_border_colour(highlighted_border_color)
.context("Update 'highlighted_border_color' in your config file..")?;
}
if let Some(text_color) = &colours.text_color {
painter
.colours
.set_text_colour(text_color)
.context("Update 'text_color' in your config file..")?;
}
if let Some(avg_cpu_color) = &colours.avg_cpu_color {
painter
.colours
.set_avg_cpu_colour(avg_cpu_color)
.context("Update 'avg_cpu_color' in your config file..")?;
}
if let Some(all_cpu_color) = &colours.all_cpu_color {
painter
.colours
.set_all_cpu_colour(all_cpu_color)
.context("Update 'all_cpu_color' in your config file..")?;
}
if let Some(cpu_core_colors) = &colours.cpu_core_colors {
painter
.colours
.set_cpu_colours(cpu_core_colors)
.context("Update 'cpu_core_colors' in your config file..")?;
}
if let Some(ram_color) = &colours.ram_color {
painter
.colours
.set_ram_colour(ram_color)
.context("Update 'ram_color' in your config file..")?;
}
if let Some(swap_color) = &colours.swap_color {
painter
.colours
.set_swap_colour(swap_color)
.context("Update 'swap_color' in your config file..")?;
}
if let Some(rx_color) = &colours.rx_color {
painter
.colours
.set_rx_colour(rx_color)
.context("Update 'rx_color' in your config file..")?;
}
if let Some(tx_color) = &colours.tx_color {
painter
.colours
.set_tx_colour(tx_color)
.context("Update 'tx_color' in your config file..")?;
}
// if let Some(rx_total_color) = &colours.rx_total_color {
// painter.colours.set_rx_total_colour(rx_total_color)?;
// }
// if let Some(tx_total_color) = &colours.tx_total_color {
// painter.colours.set_tx_total_colour(tx_total_color)?;
// }
if let Some(table_header_color) = &colours.table_header_color {
painter
.colours
.set_table_header_colour(table_header_color)
.context("Update 'table_header_color' in your config file..")?;
}
if let Some(scroll_entry_text_color) = &colours.selected_text_color {
painter
.colours
.set_scroll_entry_text_color(scroll_entry_text_color)
.context("Update 'selected_text_color' in your config file..")?;
}
if let Some(scroll_entry_bg_color) = &colours.selected_bg_color {
painter
.colours
.set_scroll_entry_bg_color(scroll_entry_bg_color)
.context("Update 'selected_bg_color' in your config file..")?;
}
if let Some(widget_title_color) = &colours.widget_title_color {
painter
.colours
.set_widget_title_colour(widget_title_color)
.context("Update 'widget_title_color' in your config file..")?;
}
if let Some(graph_color) = &colours.graph_color {
painter
.colours
.set_graph_colour(graph_color)
.context("Update 'graph_color' in your config file..")?;
}
if let Some(battery_colors) = &colours.battery_colors {
painter
.colours
.set_battery_colors(battery_colors)
.context("Update 'battery_colors' in your config file.")?;
}
}
Ok(())
}
pub fn cleanup_terminal(
terminal: &mut tui::terminal::Terminal<tui::backend::CrosstermBackend<std::io::Stdout>>,
) -> error::Result<()> {
@ -712,11 +584,11 @@ pub fn create_input_thread(
});
}
pub fn create_event_thread(
pub fn create_collection_thread(
sender: std::sync::mpsc::Sender<
BottomEvent<crossterm::event::KeyEvent, crossterm::event::MouseEvent>,
>,
reset_receiver: std::sync::mpsc::Receiver<ResetEvent>,
reset_receiver: std::sync::mpsc::Receiver<CollectionThreadEvent>,
app_config_fields: &app::AppConfigFields, used_widget_set: UsedWidgets,
) {
let temp_type = app_config_fields.temperature_type.clone();
@ -733,11 +605,24 @@ pub fn create_event_thread(
data_state.init();
loop {
let mut update_time = update_rate_in_milliseconds;
if let Ok(message) = reset_receiver.try_recv() {
match message {
ResetEvent::Reset => {
CollectionThreadEvent::Reset => {
data_state.data.first_run_cleanup();
}
CollectionThreadEvent::UpdateConfig(app_config_fields) => {
data_state.set_temperature_type(app_config_fields.temperature_type.clone());
data_state
.set_use_current_cpu_total(app_config_fields.use_current_cpu_total);
data_state.set_show_average_cpu(app_config_fields.show_average_cpu);
}
CollectionThreadEvent::UpdateUsedWidgets(used_widget_set) => {
data_state.set_collected_data(*used_widget_set);
}
CollectionThreadEvent::UpdateUpdateTime(new_time) => {
update_time = new_time;
}
}
}
futures::executor::block_on(data_state.update_data());
@ -746,7 +631,7 @@ pub fn create_event_thread(
if sender.send(event).is_err() {
break;
}
thread::sleep(Duration::from_millis(update_rate_in_milliseconds));
thread::sleep(Duration::from_millis(update_time));
}
});
}

View file

@ -1,7 +1,10 @@
use regex::Regex;
use serde::Deserialize;
use std::collections::{HashMap, HashSet};
use serde::{Deserialize, Serialize};
use std::time::Instant;
use std::{
collections::{HashMap, HashSet},
path::PathBuf,
};
use crate::{
app::{layout_manager::*, *},
@ -15,7 +18,7 @@ pub mod layout_options;
use anyhow::{Context, Result};
#[derive(Default, Deserialize)]
#[derive(Clone, Default, Deserialize, Serialize)]
pub struct Config {
pub flags: Option<ConfigFlags>,
pub colors: Option<ConfigColours>,
@ -24,7 +27,7 @@ pub struct Config {
pub temp_filter: Option<IgnoreList>,
}
#[derive(Default, Deserialize)]
#[derive(Clone, Default, Deserialize, Serialize)]
pub struct ConfigFlags {
pub hide_avg_cpu: Option<bool>,
pub dot_marker: Option<bool>,
@ -48,9 +51,10 @@ pub struct ConfigFlags {
pub hide_table_gap: Option<bool>,
pub battery: Option<bool>,
pub disable_click: Option<bool>,
pub no_write: Option<bool>,
}
#[derive(Default, Deserialize)]
#[derive(Clone, Default, Deserialize, Serialize)]
pub struct ConfigColours {
pub table_header_color: Option<String>,
pub all_cpu_color: Option<String>,
@ -72,7 +76,7 @@ pub struct ConfigColours {
pub battery_colors: Option<Vec<String>>,
}
#[derive(Default, Deserialize)]
#[derive(Clone, Default, Deserialize, Serialize)]
pub struct IgnoreList {
pub is_list_ignored: bool,
pub list: Vec<String>,
@ -83,6 +87,7 @@ pub struct IgnoreList {
pub fn build_app(
matches: &clap::ArgMatches<'static>, config: &Config, widget_layout: &BottomLayout,
default_widget_id: u64, default_widget_type_option: &Option<BottomWidgetType>,
config_path: Option<PathBuf>,
) -> Result<App> {
use BottomWidgetType::*;
let autohide_time = get_autohide_time(&matches, &config);
@ -248,6 +253,7 @@ pub fn build_app(
1
},
disable_click: get_disable_click(matches, config),
no_write: get_no_write(matches, config),
};
let used_widgets = UsedWidgets {
@ -275,13 +281,15 @@ pub fn build_app(
.temp_state(TempState::init(temp_state_map))
.battery_state(BatteryState::init(battery_state_map))
.basic_table_widget_state(basic_table_widget_state)
.current_widget(widget_map.get(&initial_widget_id).unwrap().clone()) // I think the unwrap is fine here
.current_widget(widget_map.get(&initial_widget_id).unwrap().clone()) // FIXME: [UNWRAP] - many of the unwraps are fine (like this one) but do a once-over and/or switch to expect?
.widget_map(widget_map)
.used_widgets(used_widgets)
.filters(DataFilters {
disk_filter,
temp_filter,
})
.config(config.clone())
.config_path(config_path)
.build())
}
@ -353,7 +361,7 @@ pub fn get_widget_layout(
fn get_update_rate_in_milliseconds(
matches: &clap::ArgMatches<'static>, config: &Config,
) -> error::Result<u64> {
let update_rate_in_milliseconds = if let Some(update_rate) = matches.value_of("RATE_MILLIS") {
let update_rate_in_milliseconds = if let Some(update_rate) = matches.value_of("rate") {
update_rate.parse::<u128>()?
} else if let Some(flags) = &config.flags {
if let Some(rate) = flags.rate {
@ -381,11 +389,11 @@ fn get_update_rate_in_milliseconds(
fn get_temperature(
matches: &clap::ArgMatches<'static>, config: &Config,
) -> error::Result<data_harvester::temperature::TemperatureType> {
if matches.is_present("FAHRENHEIT") {
if matches.is_present("fahrenheit") {
return Ok(data_harvester::temperature::TemperatureType::Fahrenheit);
} else if matches.is_present("KELVIN") {
} else if matches.is_present("kelvin") {
return Ok(data_harvester::temperature::TemperatureType::Kelvin);
} else if matches.is_present("CELSIUS") {
} else if matches.is_present("celsius") {
return Ok(data_harvester::temperature::TemperatureType::Celsius);
} else if let Some(flags) = &config.flags {
if let Some(temp_type) = &flags.temperature_type {
@ -406,7 +414,7 @@ fn get_temperature(
/// Yes, this function gets whether to show average CPU (true) or not (false)
fn get_show_average_cpu(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
if matches.is_present("HIDE_AVG_CPU") {
if matches.is_present("hide_avg_cpu") {
return false;
} else if let Some(flags) = &config.flags {
if let Some(avg_cpu) = flags.hide_avg_cpu {
@ -418,7 +426,7 @@ fn get_show_average_cpu(matches: &clap::ArgMatches<'static>, config: &Config) ->
}
fn get_use_dot(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
if matches.is_present("DOT_MARKER") {
if matches.is_present("dot_marker") {
return true;
} else if let Some(flags) = &config.flags {
if let Some(dot_marker) = flags.dot_marker {
@ -429,7 +437,7 @@ fn get_use_dot(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
}
fn get_use_left_legend(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
if matches.is_present("LEFT_LEGEND") {
if matches.is_present("left_legend") {
return true;
} else if let Some(flags) = &config.flags {
if let Some(left_legend) = flags.left_legend {
@ -441,7 +449,7 @@ fn get_use_left_legend(matches: &clap::ArgMatches<'static>, config: &Config) ->
}
fn get_use_current_cpu_total(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
if matches.is_present("USE_CURR_USAGE") {
if matches.is_present("current_usage") {
return true;
} else if let Some(flags) = &config.flags {
if let Some(current_usage) = flags.current_usage {
@ -453,7 +461,7 @@ fn get_use_current_cpu_total(matches: &clap::ArgMatches<'static>, config: &Confi
}
fn get_use_basic_mode(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
if matches.is_present("BASIC_MODE") {
if matches.is_present("basic") {
return true;
} else if let Some(flags) = &config.flags {
if let Some(basic) = flags.basic {
@ -467,7 +475,7 @@ fn get_use_basic_mode(matches: &clap::ArgMatches<'static>, config: &Config) -> b
fn get_default_time_value(
matches: &clap::ArgMatches<'static>, config: &Config,
) -> error::Result<u64> {
let default_time = if let Some(default_time_value) = matches.value_of("DEFAULT_TIME_VALUE") {
let default_time = if let Some(default_time_value) = matches.value_of("default_time_value") {
default_time_value.parse::<u128>()?
} else if let Some(flags) = &config.flags {
if let Some(default_time_value) = flags.default_time_value {
@ -494,7 +502,7 @@ fn get_default_time_value(
}
fn get_time_interval(matches: &clap::ArgMatches<'static>, config: &Config) -> error::Result<u64> {
let time_interval = if let Some(time_interval) = matches.value_of("TIME_DELTA") {
let time_interval = if let Some(time_interval) = matches.value_of("time_delta") {
time_interval.parse::<u128>()?
} else if let Some(flags) = &config.flags {
if let Some(time_interval) = flags.time_delta {
@ -521,7 +529,7 @@ fn get_time_interval(matches: &clap::ArgMatches<'static>, config: &Config) -> er
}
pub fn get_app_grouping(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
if matches.is_present("GROUP_PROCESSES") {
if matches.is_present("group") {
return true;
} else if let Some(flags) = &config.flags {
if let Some(grouping) = flags.group_processes {
@ -532,7 +540,7 @@ pub fn get_app_grouping(matches: &clap::ArgMatches<'static>, config: &Config) ->
}
pub fn get_app_case_sensitive(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
if matches.is_present("CASE_SENSITIVE") {
if matches.is_present("case_sensitive") {
return true;
} else if let Some(flags) = &config.flags {
if let Some(case_sensitive) = flags.case_sensitive {
@ -543,7 +551,7 @@ pub fn get_app_case_sensitive(matches: &clap::ArgMatches<'static>, config: &Conf
}
pub fn get_app_match_whole_word(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
if matches.is_present("WHOLE_WORD") {
if matches.is_present("whole_word") {
return true;
} else if let Some(flags) = &config.flags {
if let Some(whole_word) = flags.whole_word {
@ -554,7 +562,7 @@ pub fn get_app_match_whole_word(matches: &clap::ArgMatches<'static>, config: &Co
}
pub fn get_app_use_regex(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
if matches.is_present("REGEX_DEFAULT") {
if matches.is_present("regex") {
return true;
} else if let Some(flags) = &config.flags {
if let Some(regex) = flags.regex {
@ -565,7 +573,7 @@ pub fn get_app_use_regex(matches: &clap::ArgMatches<'static>, config: &Config) -
}
fn get_hide_time(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
if matches.is_present("HIDE_TIME") {
if matches.is_present("hide_time") {
return true;
} else if let Some(flags) = &config.flags {
if let Some(hide_time) = flags.hide_time {
@ -576,7 +584,7 @@ fn get_hide_time(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
}
fn get_autohide_time(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
if matches.is_present("AUTOHIDE_TIME") {
if matches.is_present("autohide_time") {
return true;
} else if let Some(flags) = &config.flags {
if let Some(autohide_time) = flags.autohide_time {
@ -590,7 +598,7 @@ fn get_autohide_time(matches: &clap::ArgMatches<'static>, config: &Config) -> bo
fn get_default_widget_and_count(
matches: &clap::ArgMatches<'static>, config: &Config,
) -> error::Result<(Option<BottomWidgetType>, u64)> {
let widget_type = if let Some(widget_type) = matches.value_of("DEFAULT_WIDGET_TYPE") {
let widget_type = if let Some(widget_type) = matches.value_of("default_widget_type") {
let parsed_widget = widget_type.parse::<BottomWidgetType>()?;
if let BottomWidgetType::Empty = parsed_widget {
None
@ -612,7 +620,7 @@ fn get_default_widget_and_count(
None
};
let widget_count = if let Some(widget_count) = matches.value_of("DEFAULT_WIDGET_COUNT") {
let widget_count = if let Some(widget_count) = matches.value_of("default_widget_count") {
Some(widget_count.parse::<u128>()?)
} else if let Some(flags) = &config.flags {
if let Some(widget_count) = flags.default_widget_count {
@ -643,7 +651,7 @@ fn get_default_widget_and_count(
}
fn get_disable_click(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
if matches.is_present("DISABLE_CLICK") {
if matches.is_present("disable_click") {
return true;
} else if let Some(flags) = &config.flags {
if let Some(disable_click) = flags.disable_click {
@ -654,7 +662,7 @@ fn get_disable_click(matches: &clap::ArgMatches<'static>, config: &Config) -> bo
}
pub fn get_use_old_network_legend(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
if matches.is_present("USE_OLD_NETWORK_LEGEND") {
if matches.is_present("use_old_network_legend") {
return true;
} else if let Some(flags) = &config.flags {
if let Some(use_old_network_legend) = flags.use_old_network_legend {
@ -665,7 +673,7 @@ pub fn get_use_old_network_legend(matches: &clap::ArgMatches<'static>, config: &
}
pub fn get_hide_table_gap(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
if matches.is_present("HIDE_TABLE_GAP") {
if matches.is_present("hide_table_gap") {
return true;
} else if let Some(flags) = &config.flags {
if let Some(hide_table_gap) = flags.hide_table_gap {
@ -676,7 +684,7 @@ pub fn get_hide_table_gap(matches: &clap::ArgMatches<'static>, config: &Config)
}
pub fn get_use_battery(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
if matches.is_present("BATTERY") {
if matches.is_present("battery") {
return true;
} else if let Some(flags) = &config.flags {
if let Some(battery) = flags.battery {
@ -686,6 +694,17 @@ pub fn get_use_battery(matches: &clap::ArgMatches<'static>, config: &Config) ->
false
}
pub fn get_no_write(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
if matches.is_present("no_write") {
return true;
} else if let Some(flags) = &config.flags {
if let Some(no_write) = flags.no_write {
return no_write;
}
}
false
}
pub fn get_ignore_list(ignore_list: &Option<IgnoreList>) -> error::Result<Option<Filter>> {
if let Some(ignore_list) = ignore_list {
let list: Result<Vec<_>, _> = ignore_list

View file

@ -1,10 +1,10 @@
use crate::app::layout_manager::*;
use crate::error::Result;
use serde::Deserialize;
use serde::{Deserialize, Serialize};
/// Represents a row. This has a length of some sort (optional) and a vector
/// of children.
#[derive(Deserialize, Debug)]
#[derive(Clone, Deserialize, Debug, Serialize)]
#[serde(rename = "row")]
pub struct Row {
pub ratio: Option<u32>,
@ -334,7 +334,7 @@ impl Row {
/// A Col can also have an optional length and children. We only allow columns
/// to have FinalWidgets as children, lest we get some amount of mutual
/// recursion between Row and Col.
#[derive(Deserialize, Debug)]
#[derive(Clone, Deserialize, Debug, Serialize)]
#[serde(untagged)]
pub enum RowChildren {
Widget(FinalWidget),
@ -345,7 +345,7 @@ pub enum RowChildren {
}
/// Represents a widget.
#[derive(Deserialize, Debug)]
#[derive(Clone, Deserialize, Debug, Serialize)]
pub struct FinalWidget {
pub ratio: Option<u32>,
#[serde(rename = "type")]