feature: add nord and nord-light colours (#406)

Adds colour schemes for Nord, along with a light variant.
This commit is contained in:
Clement Tsang 2021-02-15 14:12:43 -05:00 committed by GitHub
parent f2e6b9232d
commit fb7b1226fd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 211 additions and 148 deletions

View file

@ -9,13 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Features
- [#263](https://github.com/ClementTsang/bottom/pull/263): Adds the option for fine-grained kill signals on Unix-like systems.
- [#333](https://github.com/ClementTsang/bottom/pull/333): Adds an "out of" indicator that can be enabled using `--show_table_scroll_position` (and its corresponding config option) to help keep track of scrolled position.
- [#379](https://github.com/ClementTsang/bottom/pull/379): Adds `--process_command` flag and corresponding config option to default to showing a process' command.
- [#381](https://github.com/ClementTsang/bottom/pull/381): Adds a filter in the config file for network interfaces.
## [0.5.8] - Unreleased
- [#406](https://github.com/ClementTsang/bottom/pull/406): Adds the Nord colour scheme, as well as a light variant.
## Changes

View file

@ -564,7 +564,7 @@ The following options can be set under `[flags]` to achieve the same effect as p
These are the following supported flag config values, which correspond to the flag of the same name described in [Flags](#flags):
| Field | Type | Functionality |
| ---------------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------- |
| ---------------------------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
| `hide_avg_cpu` | Boolean | Hides the average CPU usage. |
| `dot_marker` | Boolean | Uses a dot marker for graphs. |
| `left_legend` | Boolean | Puts the CPU chart legend to the left side. |
@ -583,7 +583,7 @@ These are the following supported flag config values, which correspond to the fl
| `default_widget_type` | String (one of ["cpu", "proc", "net", "temp", "mem", "disk"], same as layout options) | Sets the default widget type, use --help for more info. |
| `default_widget_count` | Unsigned Int (represents which `default_widget_type`) | Sets the n'th selected widget type as the default. |
| `disable_click` | Boolean | Disables mouse clicks. |
| `color` | String (one of ["default", "default-light", "gruvbox", "gruvbox-light"]) | Use a color scheme, use --help for supported values. |
| `color` | String (one of ["default", "default-light", "gruvbox", "gruvbox-light", "nord", "nord-light"]) | Use a color scheme, use --help for supported values. |
| `mem_as_value` | Boolean | Defaults to showing process memory usage by value. |
| `tree` | Boolean | Defaults to showing the process widget in tree mode. |
| `show_table_scroll_position` | Boolean | Shows the scroll position tracker in table widgets. |

View file

@ -526,7 +526,7 @@ impl App {
);
}
self.did_config_fail_to_save = self.update_config_file().is_err();
// self.did_config_fail_to_save = self.update_config_file().is_err();
}
}
@ -586,7 +586,7 @@ impl App {
);
}
self.did_config_fail_to_save = self.update_config_file().is_err();
// self.did_config_fail_to_save = self.update_config_file().is_err();
}
}
@ -642,7 +642,7 @@ impl App {
);
}
self.did_config_fail_to_save = self.update_config_file().is_err();
// self.did_config_fail_to_save = self.update_config_file().is_err();
}
}
@ -1565,9 +1565,9 @@ impl App {
self.is_force_redraw = true;
}
/// TODO: Disabled.
/// Call this whenever the config value is updated!
fn update_config_file(&mut self) -> anyhow::Result<()> {
// TODO: Disabled.
// fn update_config_file(&mut self) -> anyhow::Result<()> {
// if self.app_config_fields.no_write {
// // debug!("No write enabled. Config will not be written.");
// // Don't write!
@ -1585,8 +1585,8 @@ impl App {
// "Config path was missing, please try restarting bottom..."
// ))
// }
Ok(())
}
// Ok(())
// }
pub fn kill_highlighted_process(&mut self) -> Result<()> {
if let BottomWidgetType::Proc = self.current_widget.widget_type {

View file

@ -12,20 +12,24 @@ pub struct BottomLayout {
pub total_row_height_ratio: u32,
}
type WidgetMappings = (u32, BTreeMap<(u32, u32), u64>);
type ColumnRowMappings = (u32, BTreeMap<(u32, u32), WidgetMappings>);
type ColumnMappings = (u32, BTreeMap<(u32, u32), ColumnRowMappings>);
// Represents a start and end coordinate in some dimension.
type LineSegment = (u32, u32);
type WidgetMappings = (u32, BTreeMap<LineSegment, u64>);
type ColumnRowMappings = (u32, BTreeMap<LineSegment, WidgetMappings>);
type ColumnMappings = (u32, BTreeMap<LineSegment, ColumnRowMappings>);
impl BottomLayout {
pub fn get_movement_mappings(&mut self) {
fn is_intersecting(a: (u32, u32), b: (u32, u32)) -> bool {
#[allow(clippy::suspicious_operation_groupings)] // Have to enable this, clippy really doesn't like me doing this with tuples...
fn is_intersecting(a: LineSegment, b: LineSegment) -> bool {
a.0 >= b.0 && a.1 <= b.1
|| a.1 >= b.1 && a.0 <= b.0
|| a.0 <= b.0 && a.1 >= b.0
|| a.0 >= b.0 && a.0 < b.1 && a.1 >= b.1
}
fn get_distance(target: (u32, u32), candidate: (u32, u32)) -> u32 {
fn get_distance(target: LineSegment, candidate: LineSegment) -> u32 {
if candidate.0 < target.0 {
candidate.1 - target.0
} else if candidate.1 < target.1 {
@ -38,20 +42,20 @@ impl BottomLayout {
// Now we need to create the correct mapping for moving from a specific
// widget to another
let mut layout_mapping: BTreeMap<(u32, u32), ColumnMappings> = BTreeMap::new();
let mut layout_mapping: BTreeMap<LineSegment, ColumnMappings> = BTreeMap::new();
let mut total_height = 0;
for row in &self.rows {
let mut row_width = 0;
let mut row_mapping: BTreeMap<(u32, u32), ColumnRowMappings> = BTreeMap::new();
let mut row_mapping: BTreeMap<LineSegment, ColumnRowMappings> = BTreeMap::new();
let mut is_valid_row = false;
for col in &row.children {
let mut col_row_height = 0;
let mut col_mapping: BTreeMap<(u32, u32), WidgetMappings> = BTreeMap::new();
let mut col_mapping: BTreeMap<LineSegment, WidgetMappings> = BTreeMap::new();
let mut is_valid_col = false;
for col_row in &col.children {
let mut widget_width = 0;
let mut col_row_mapping: BTreeMap<(u32, u32), u64> = BTreeMap::new();
let mut col_row_mapping: BTreeMap<LineSegment, u64> = BTreeMap::new();
let mut is_valid_col_row = false;
for widget in &col_row.children {
match widget.widget_type {

View file

@ -27,7 +27,11 @@ impl Process {
}
fn kill(self) -> Result<(), String> {
unsafe { TerminateProcess(self.0, 1) };
let result = unsafe { TerminateProcess(self.0, 1) };
if result == 0 {
return Err("Failed to kill process".to_string());
}
Ok(())
}
}

View file

@ -625,8 +625,8 @@ impl Prefix {
is_ignoring_case,
is_searching_with_regex,
);
} else if let Some((prefix_type, query_content)) = &mut self.regex_prefix {
if let StringQuery::Value(regex_string) = query_content {
} else if let Some((prefix_type, StringQuery::Value(regex_string))) = &mut self.regex_prefix
{
match prefix_type {
PrefixType::Pid | PrefixType::Name | PrefixType::State => {
let escaped_regex: String;
@ -654,7 +654,6 @@ impl Prefix {
_ => {}
}
}
}
Ok(())
}

View file

@ -66,7 +66,8 @@ pub enum ColourScheme {
DefaultLight,
Gruvbox,
GruvboxLight,
// Nord,
Nord,
NordLight,
Custom,
}
@ -80,7 +81,8 @@ impl FromStr for ColourScheme {
"default-light" => Ok(ColourScheme::DefaultLight),
"gruvbox" => Ok(ColourScheme::Gruvbox),
"gruvbox-light" => Ok(ColourScheme::GruvboxLight),
// "nord" => Ok(ColourScheme::Nord),
"nord" => Ok(ColourScheme::Nord),
"nord-light" => Ok(ColourScheme::NordLight),
_ => Err(BottomError::ConfigError(format!(
"\"{}\" is an invalid built-in color scheme.",
s
@ -227,10 +229,14 @@ impl Painter {
self.colours
.set_colours_from_palette(&*GRUVBOX_LIGHT_COLOUR_PALETTE)?;
}
// ColourScheme::Nord => {
// self.colours
// .set_colours_from_palette(&*NORD_COLOUR_PALETTE)?;
// }
ColourScheme::Nord => {
self.colours
.set_colours_from_palette(&*NORD_COLOUR_PALETTE)?;
}
ColourScheme::NordLight => {
self.colours
.set_colours_from_palette(&*NORD_LIGHT_COLOUR_PALETTE)?;
}
ColourScheme::Custom => {
// This case should never occur, just do nothing.
}

View file

@ -231,11 +231,22 @@ Use a pre-defined color scheme. Currently supported values are:
+------------------------------------------------------------+
| gruvbox-light (gruvbox but for use with light backgrounds) |
+------------------------------------------------------------+
| nord (an arctic, north-bluish color palette) |
+------------------------------------------------------------+
| nord-light (nord but for use with light backgrounds) |
+------------------------------------------------------------+
Defaults to \"default\".
\n\n",
)
.possible_values(&["default", "default-light", "gruvbox", "gruvbox-light"])
.possible_values(&[
"default",
"default-light",
"gruvbox",
"gruvbox-light",
"nord",
"nord-light",
])
.hide_possible_values(true);
let mem_as_value = Arg::with_name("mem_as_value")
.long("mem_as_value")

View file

@ -45,6 +45,7 @@ pub static DEFAULT_LIGHT_MODE_COLOUR_PALETTE: Lazy<ConfigColours> = Lazy::new(||
disabled_text_color: Some("gray".to_string()),
..ConfigColours::default()
});
pub static GRUVBOX_COLOUR_PALETTE: Lazy<ConfigColours> = Lazy::new(|| ConfigColours {
table_header_color: Some("#83a598".to_string()),
all_cpu_color: Some("#8ec07c".to_string()),
@ -89,6 +90,7 @@ pub static GRUVBOX_COLOUR_PALETTE: Lazy<ConfigColours> = Lazy::new(|| ConfigColo
medium_battery_color: Some("#fabd2f".to_string()),
low_battery_color: Some("#fb4934".to_string()),
});
pub static GRUVBOX_LIGHT_COLOUR_PALETTE: Lazy<ConfigColours> = Lazy::new(|| ConfigColours {
table_header_color: Some("#076678".to_string()),
all_cpu_color: Some("#8ec07c".to_string()),
@ -134,6 +136,72 @@ pub static GRUVBOX_LIGHT_COLOUR_PALETTE: Lazy<ConfigColours> = Lazy::new(|| Conf
low_battery_color: Some("#cc241d".to_string()),
});
pub static NORD_COLOUR_PALETTE: Lazy<ConfigColours> = Lazy::new(|| ConfigColours {
table_header_color: Some("#81a1c1".to_string()),
all_cpu_color: Some("#88c0d0".to_string()),
avg_cpu_color: Some("#8fbcbb".to_string()),
cpu_core_colors: Some(vec![
"#5e81ac".to_string(),
"#81a1c1".to_string(),
"#d8dee9".to_string(),
"#b48ead".to_string(),
"#a3be8c".to_string(),
"#ebcb8b".to_string(),
"#d08770".to_string(),
"#bf616a".to_string(),
]),
ram_color: Some("#88c0d0".to_string()),
swap_color: Some("#d08770".to_string()),
rx_color: Some("#88c0d0".to_string()),
tx_color: Some("#d08770".to_string()),
rx_total_color: Some("#5e81ac".to_string()),
tx_total_color: Some("#8fbcbb".to_string()),
border_color: Some("#e5e9f0".to_string()),
highlighted_border_color: Some("#88c0d0".to_string()),
disabled_text_color: Some("#4c566a".to_string()),
text_color: Some("#e5e9f0".to_string()),
selected_text_color: Some("#2e3440".to_string()),
selected_bg_color: Some("#88c0d0".to_string()),
widget_title_color: Some("#e5e9f0".to_string()),
graph_color: Some("#e5e9f0".to_string()),
high_battery_color: Some("#a3be8c".to_string()),
medium_battery_color: Some("#ebcb8b".to_string()),
low_battery_color: Some("#bf616a".to_string()),
});
pub static NORD_LIGHT_COLOUR_PALETTE: Lazy<ConfigColours> = Lazy::new(|| ConfigColours {
table_header_color: Some("#5e81ac".to_string()),
all_cpu_color: Some("#81a1c1".to_string()),
avg_cpu_color: Some("#8fbcbb".to_string()),
cpu_core_colors: Some(vec![
"#5e81ac".to_string(),
"#88c0d0".to_string(),
"#4c566a".to_string(),
"#b48ead".to_string(),
"#a3be8c".to_string(),
"#ebcb8b".to_string(),
"#d08770".to_string(),
"#bf616a".to_string(),
]),
ram_color: Some("#81a1c1".to_string()),
swap_color: Some("#d08770".to_string()),
rx_color: Some("#81a1c1".to_string()),
tx_color: Some("#d08770".to_string()),
rx_total_color: Some("#5e81ac".to_string()),
tx_total_color: Some("#8fbcbb".to_string()),
border_color: Some("#2e3440".to_string()),
highlighted_border_color: Some("#5e81ac".to_string()),
disabled_text_color: Some("#d8dee9".to_string()),
text_color: Some("#2e3440".to_string()),
selected_text_color: Some("#f5f5f5".to_string()),
selected_bg_color: Some("#5e81ac".to_string()),
widget_title_color: Some("#2e3440".to_string()),
graph_color: Some("#2e3440".to_string()),
high_battery_color: Some("#a3be8c".to_string()),
medium_battery_color: Some("#ebcb8b".to_string()),
low_battery_color: Some("#bf616a".to_string()),
});
// Help text
pub const HELP_CONTENTS_TEXT: [&str; 8] = [
"Press the corresponding numbers to jump to the section, or scroll:",

View file

@ -11,7 +11,7 @@ fn get_binary_location() -> String {
}
#[test]
fn test_small_rate() -> Result<(), Box<dyn std::error::Error>> {
fn test_small_rate() {
Command::new(get_binary_location())
.arg("-r")
.arg("249")
@ -20,11 +20,10 @@ fn test_small_rate() -> Result<(), Box<dyn std::error::Error>> {
.stderr(predicate::str::contains(
"set your update rate to be at least 250 milliseconds.",
));
Ok(())
}
#[test]
fn test_large_default_time() -> Result<(), Box<dyn std::error::Error>> {
fn test_large_default_time() {
Command::new(get_binary_location())
.arg("-t")
.arg("18446744073709551616")
@ -33,11 +32,10 @@ fn test_large_default_time() -> Result<(), Box<dyn std::error::Error>> {
.stderr(predicate::str::contains(
"set your default value to be at most",
));
Ok(())
}
#[test]
fn test_small_default_time() -> Result<(), Box<dyn std::error::Error>> {
fn test_small_default_time() {
Command::new(get_binary_location())
.arg("-t")
.arg("900")
@ -46,11 +44,10 @@ fn test_small_default_time() -> Result<(), Box<dyn std::error::Error>> {
.stderr(predicate::str::contains(
"set your default value to be at least",
));
Ok(())
}
#[test]
fn test_large_delta_time() -> Result<(), Box<dyn std::error::Error>> {
fn test_large_delta_time() {
Command::new(get_binary_location())
.arg("-d")
.arg("18446744073709551616")
@ -59,11 +56,10 @@ fn test_large_delta_time() -> Result<(), Box<dyn std::error::Error>> {
.stderr(predicate::str::contains(
"set your time delta to be at most",
));
Ok(())
}
#[test]
fn test_small_delta_time() -> Result<(), Box<dyn std::error::Error>> {
fn test_small_delta_time() {
Command::new(get_binary_location())
.arg("-d")
.arg("900")
@ -72,11 +68,10 @@ fn test_small_delta_time() -> Result<(), Box<dyn std::error::Error>> {
.stderr(predicate::str::contains(
"set your time delta to be at least",
));
Ok(())
}
#[test]
fn test_large_rate() -> Result<(), Box<dyn std::error::Error>> {
fn test_large_rate() {
Command::new(get_binary_location())
.arg("-r")
.arg("18446744073709551616")
@ -85,11 +80,10 @@ fn test_large_rate() -> Result<(), Box<dyn std::error::Error>> {
.stderr(predicate::str::contains(
"set your update rate to be at most unsigned INT_MAX.",
));
Ok(())
}
#[test]
fn test_negative_rate() -> Result<(), Box<dyn std::error::Error>> {
fn test_negative_rate() {
// This test should auto fail due to how clap works
Command::new(get_binary_location())
.arg("-r")
@ -99,24 +93,20 @@ fn test_negative_rate() -> Result<(), Box<dyn std::error::Error>> {
.stderr(predicate::str::contains(
"wasn't expected, or isn't valid in this context",
));
Ok(())
}
#[test]
fn test_invalid_rate() -> Result<(), Box<dyn std::error::Error>> {
fn test_invalid_rate() {
Command::new(get_binary_location())
.arg("-r")
.arg("100-1000")
.assert()
.failure()
.stderr(predicate::str::contains("invalid digit"));
Ok(())
}
#[test]
fn test_conflicting_temps() -> Result<(), Box<dyn std::error::Error>> {
fn test_conflicting_temps() {
Command::new(get_binary_location())
.arg("-c")
.arg("-f")
@ -125,24 +115,20 @@ fn test_conflicting_temps() -> Result<(), Box<dyn std::error::Error>> {
.stderr(predicate::str::contains(
"cannot be used with one or more of the other specified arguments",
));
Ok(())
}
#[test]
fn test_invalid_default_widget_1() -> Result<(), Box<dyn std::error::Error>> {
fn test_invalid_default_widget_1() {
Command::new(get_binary_location())
.arg("--default_widget_type")
.arg("fake_widget")
.assert()
.failure()
.stderr(predicate::str::contains("invalid widget name"));
Ok(())
}
#[test]
fn test_invalid_default_widget_2() -> Result<(), Box<dyn std::error::Error>> {
fn test_invalid_default_widget_2() {
Command::new(get_binary_location())
.arg("--default_widget_type")
.arg("cpu")
@ -153,12 +139,10 @@ fn test_invalid_default_widget_2() -> Result<(), Box<dyn std::error::Error>> {
.stderr(predicate::str::contains(
"set your widget count to be at most unsigned INT_MAX",
));
Ok(())
}
#[test]
fn test_missing_default_widget_type() -> Result<(), Box<dyn std::error::Error>> {
fn test_missing_default_widget_type() {
Command::new(get_binary_location())
.arg("--default_widget_count")
.arg("3")
@ -167,6 +151,4 @@ fn test_missing_default_widget_type() -> Result<(), Box<dyn std::error::Error>>
.stderr(predicate::str::contains(
"The following required arguments were not provided",
));
Ok(())
}

View file

@ -9,150 +9,137 @@ fn get_binary_location() -> String {
}
#[test]
fn test_toml_mismatch_type() -> Result<(), Box<dyn std::error::Error>> {
fn test_toml_mismatch_type() {
Command::new(get_binary_location())
.arg("-C")
.arg("./tests/invalid_configs/toml_mismatch_type.toml")
.assert()
.failure()
.stderr(predicate::str::contains("invalid type"));
Ok(())
}
#[test]
fn test_empty_layout() -> Result<(), Box<dyn std::error::Error>> {
fn test_empty_layout() {
Command::new(get_binary_location())
.arg("-C")
.arg("./tests/invalid_configs/empty_layout.toml")
.assert()
.failure()
.stderr(predicate::str::contains("at least one widget"));
Ok(())
}
#[test]
fn test_invalid_layout_widget_type() -> Result<(), Box<dyn std::error::Error>> {
fn test_invalid_layout_widget_type() {
Command::new(get_binary_location())
.arg("-C")
.arg("./tests/invalid_configs/invalid_layout_widget_type.toml")
.assert()
.failure()
.stderr(predicate::str::contains("invalid widget name"));
Ok(())
}
/// This test isn't really needed as this is technically covered by TOML spec.
/// However, I feel like it's worth checking anyways - not like it takes long.
#[test]
fn test_duplicate_temp_type() -> Result<(), Box<dyn std::error::Error>> {
fn test_duplicate_temp_type() {
Command::new(get_binary_location())
.arg("-C")
.arg("./tests/invalid_configs/duplicate_temp_type.toml")
.assert()
.failure()
.stderr(predicate::str::contains("duplicate field"));
Ok(())
}
/// Checks for if a hex is valid
#[test]
fn test_invalid_colour_hex() -> Result<(), Box<dyn std::error::Error>> {
fn test_invalid_colour_hex() {
Command::new(get_binary_location())
.arg("-C")
.arg("./tests/invalid_configs/invalid_colour_hex.toml")
.assert()
.failure()
.stderr(predicate::str::contains("invalid hex colour"));
Ok(())
}
/// Checks for if a hex is too long
#[test]
fn test_invalid_colour_hex_2() -> Result<(), Box<dyn std::error::Error>> {
fn test_invalid_colour_hex_2() {
Command::new(get_binary_location())
.arg("-C")
.arg("./tests/invalid_configs/invalid_colour_hex_2.toml")
.assert()
.failure()
.stderr(predicate::str::contains("invalid hex colour"));
Ok(())
}
/// Checks unicode hex because the way we originally did it could cause char
/// boundary errors!
#[test]
fn test_invalid_colour_hex_3() -> Result<(), Box<dyn std::error::Error>> {
fn test_invalid_colour_hex_3() {
Command::new(get_binary_location())
.arg("-C")
.arg("./tests/invalid_configs/invalid_colour_hex_3.toml")
.assert()
.failure()
.stderr(predicate::str::contains("invalid hex colour"));
Ok(())
}
#[test]
fn test_invalid_colour_name() -> Result<(), Box<dyn std::error::Error>> {
fn test_invalid_colour_name() {
Command::new(get_binary_location())
.arg("-C")
.arg("./tests/invalid_configs/invalid_colour_name.toml")
.assert()
.failure()
.stderr(predicate::str::contains("invalid named colour"));
Ok(())
}
#[test]
fn test_invalid_colour_rgb() -> Result<(), Box<dyn std::error::Error>> {
fn test_invalid_colour_rgb() {
Command::new(get_binary_location())
.arg("-C")
.arg("./tests/invalid_configs/invalid_colour_rgb.toml")
.assert()
.failure()
.stderr(predicate::str::contains("invalid RGB"));
Ok(())
}
#[test]
fn test_invalid_colour_rgb_2() -> Result<(), Box<dyn std::error::Error>> {
fn test_invalid_colour_rgb_2() {
Command::new(get_binary_location())
.arg("-C")
.arg("./tests/invalid_configs/invalid_colour_rgb_2.toml")
.assert()
.failure()
.stderr(predicate::str::contains("invalid RGB"));
Ok(())
}
#[test]
fn test_invalid_colour_string() -> Result<(), Box<dyn std::error::Error>> {
fn test_invalid_colour_string() {
Command::new(get_binary_location())
.arg("-C")
.arg("./tests/invalid_configs/invalid_colour_string.toml")
.assert()
.failure()
.stderr(predicate::str::contains("invalid named colour"));
Ok(())
}
#[test]
fn test_lone_default_widget_count() -> Result<(), Box<dyn std::error::Error>> {
fn test_lone_default_widget_count() {
Command::new(get_binary_location())
.arg("-C")
.arg("./tests/invalid_configs/lone_default_widget_count.toml")
.assert()
.failure()
.stderr(predicate::str::contains("it must be used with"));
Ok(())
}
#[test]
fn test_invalid_default_widget_count() -> Result<(), Box<dyn std::error::Error>> {
fn test_invalid_default_widget_count() {
Command::new(get_binary_location())
.arg("-C")
.arg("./tests/invalid_configs/invalid_default_widget_count.toml")
.assert()
.failure()
.stderr(predicate::str::contains("invalid number"));
Ok(())
}