Merge branch 'master' into conditional-style

This commit is contained in:
Filip Bachul 2022-09-29 09:41:48 +02:00
commit 35055fe7db
8 changed files with 172 additions and 18 deletions

View File

@ -1551,6 +1551,22 @@
"add_newline": { "add_newline": {
"default": true, "default": true,
"type": "boolean" "type": "boolean"
},
"palette": {
"type": [
"string",
"null"
]
},
"palettes": {
"default": {},
"type": "object",
"additionalProperties": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
} }
}, },
"additionalProperties": false, "additionalProperties": false,

4
Cargo.lock generated
View File

@ -1814,9 +1814,9 @@ checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
[[package]] [[package]]
name = "notify-rust" name = "notify-rust"
version = "4.5.9" version = "4.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89981320931e5f40cf7cf42a75de9ea445d2c61acd3a819fd4cd22cd8a610998" checksum = "368e89ea58df747ce88be669ae44e79783c1d30bfd540ad0fc520b3f41f0b3b0"
dependencies = [ dependencies = [
"mac-notification-sys", "mac-notification-sys",
"serde", "serde",

View File

@ -49,7 +49,7 @@ local_ipaddress = "0.1.3"
log = { version = "0.4.16", features = ["std"] } log = { version = "0.4.16", features = ["std"] }
# nofity-rust is optional (on by default) because the crate doesn't currently build for darwin with nix # nofity-rust is optional (on by default) because the crate doesn't currently build for darwin with nix
# see: https://github.com/NixOS/nixpkgs/issues/160876 # see: https://github.com/NixOS/nixpkgs/issues/160876
notify-rust = { version = "4.5.9", optional = true } notify-rust = { version = "4.5.10", optional = true }
nu-ansi-term = "0.46.0" nu-ansi-term = "0.46.0"
once_cell = "1.15.0" once_cell = "1.15.0"
open = "3.0.3" open = "3.0.3"

View File

@ -177,12 +177,14 @@ This is the list of prompt-wide configuration options.
### Options ### Options
| Option | Default | Description | | Option | Default | Description |
| ----------------- | ------------------------------ | ---------------------------------------------------------------- | | ----------------- | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `format` | [link](#default-prompt-format) | Configure the format of the prompt. | | `format` | [link](#default-prompt-format) | Configure the format of the prompt. |
| `right_format` | `""` | See [Enable Right Prompt](/advanced-config/#enable-right-prompt) | | `right_format` | `""` | See [Enable Right Prompt](/advanced-config/#enable-right-prompt) |
| `scan_timeout` | `30` | Timeout for starship to scan files (in milliseconds). | | `scan_timeout` | `30` | Timeout for starship to scan files (in milliseconds). |
| `command_timeout` | `500` | Timeout for commands executed by starship (in milliseconds). | | `command_timeout` | `500` | Timeout for commands executed by starship (in milliseconds). |
| `add_newline` | `true` | Inserts blank line between shell prompts. | | `add_newline` | `true` | Inserts blank line between shell prompts. |
| `palette` | `""` | Sets which color palette from `palettes` to use. |
| `palettes` | `{}` | Collection of color palettes that assign [colors](/advanced-config/#style-strings) to user-defined names. Note that color palettes cannot reference their own color definitions. |
### Example ### Example
@ -200,6 +202,16 @@ scan_timeout = 10
# Disable the blank line at the start of the prompt # Disable the blank line at the start of the prompt
add_newline = false add_newline = false
# Set "foo" as custom color palette
palette = "foo"
# Define custom colors
[palettes.foo]
# Overwrite existing color
blue = "21"
# Define new color
mustard = "#af8700"
``` ```
### Default Prompt Format ### Default Prompt Format

View File

@ -1,3 +1,5 @@
use crate::configs::Palette;
use crate::context::Context;
use crate::serde_utils::ValueDeserializer; use crate::serde_utils::ValueDeserializer;
use crate::utils; use crate::utils;
use nu_ansi_term::Color; use nu_ansi_term::Color;
@ -7,6 +9,7 @@ use serde::{
use std::borrow::Cow; use std::borrow::Cow;
use std::clone::Clone; use std::clone::Clone;
use std::collections::HashMap;
use std::io::ErrorKind; use std::io::ErrorKind;
use std::env; use std::env;
@ -260,7 +263,7 @@ where
D: Deserializer<'de>, D: Deserializer<'de>,
{ {
Cow::<'_, str>::deserialize(de).and_then(|s| { Cow::<'_, str>::deserialize(de).and_then(|s| {
parse_style_string(s.as_ref()).ok_or_else(|| D::Error::custom("Invalid style string")) parse_style_string(s.as_ref(), None).ok_or_else(|| D::Error::custom("Invalid style string"))
}) })
} }
@ -275,7 +278,10 @@ where
- 'blink' - 'blink'
- '<color>' (see the `parse_color_string` doc for valid color strings) - '<color>' (see the `parse_color_string` doc for valid color strings)
*/ */
pub fn parse_style_string(style_string: &str) -> Option<nu_ansi_term::Style> { pub fn parse_style_string(
style_string: &str,
context: Option<&Context>,
) -> Option<nu_ansi_term::Style> {
style_string style_string
.split_whitespace() .split_whitespace()
.fold(Some(nu_ansi_term::Style::new()), |maybe_style, token| { .fold(Some(nu_ansi_term::Style::new()), |maybe_style, token| {
@ -308,7 +314,15 @@ pub fn parse_style_string(style_string: &str) -> Option<nu_ansi_term::Style> {
None // fg:none yields no style. None // fg:none yields no style.
} else { } else {
// Either bg or valid color or both. // Either bg or valid color or both.
let parsed = parse_color_string(color_string); let parsed = parse_color_string(
color_string,
context.and_then(|x| {
get_palette(
&x.root_config.palettes,
x.root_config.palette.as_deref(),
)
}),
);
// bg + invalid color = reset the background to default. // bg + invalid color = reset the background to default.
if !col_fg && parsed.is_none() { if !col_fg && parsed.is_none() {
let mut new_style = style; let mut new_style = style;
@ -335,9 +349,12 @@ pub fn parse_style_string(style_string: &str) -> Option<nu_ansi_term::Style> {
There are three valid color formats: There are three valid color formats:
- #RRGGBB (a hash followed by an RGB hex) - #RRGGBB (a hash followed by an RGB hex)
- u8 (a number from 0-255, representing an ANSI color) - u8 (a number from 0-255, representing an ANSI color)
- colstring (one of the 16 predefined color strings) - colstring (one of the 16 predefined color strings or a custom user-defined color)
*/ */
fn parse_color_string(color_string: &str) -> Option<nu_ansi_term::Color> { fn parse_color_string(
color_string: &str,
palette: Option<&Palette>,
) -> Option<nu_ansi_term::Color> {
// Parse RGB hex values // Parse RGB hex values
log::trace!("Parsing color_string: {}", color_string); log::trace!("Parsing color_string: {}", color_string);
if color_string.starts_with('#') { if color_string.starts_with('#') {
@ -362,6 +379,16 @@ fn parse_color_string(color_string: &str) -> Option<nu_ansi_term::Color> {
return Some(Color::Fixed(ansi_color_num)); return Some(Color::Fixed(ansi_color_num));
} }
// Check palette for a matching user-defined color
if let Some(palette_color) = palette.as_ref().and_then(|x| x.get(color_string)) {
log::trace!(
"Read user-defined color string: {} defined as {}",
color_string,
palette_color
);
return parse_color_string(palette_color, None);
}
// Check for any predefined color strings // Check for any predefined color strings
// There are no predefined enums for bright colors, so we use Color::Fixed // There are no predefined enums for bright colors, so we use Color::Fixed
let predefined_color = match color_string.to_lowercase().as_str() { let predefined_color = match color_string.to_lowercase().as_str() {
@ -392,6 +419,24 @@ fn parse_color_string(color_string: &str) -> Option<nu_ansi_term::Color> {
predefined_color predefined_color
} }
fn get_palette<'a>(
palettes: &'a HashMap<String, Palette>,
palette_name: Option<&str>,
) -> Option<&'a Palette> {
if let Some(palette_name) = palette_name {
let palette = palettes.get(palette_name);
if palette.is_some() {
log::trace!("Found color palette: {}", palette_name);
} else {
log::warn!("Could not find color palette: {}", palette_name);
}
palette
} else {
log::trace!("No color palette specified, using defaults");
None
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -778,4 +823,76 @@ mod tests {
Style::new().fg(Color::Fixed(125)).on(Color::Fixed(127)) Style::new().fg(Color::Fixed(125)).on(Color::Fixed(127))
); );
} }
#[test]
fn table_get_colors_palette() {
// Test using colors defined in palette
let mut palette = Palette::new();
palette.insert("mustard".to_string(), "#af8700".to_string());
palette.insert("sky-blue".to_string(), "51".to_string());
palette.insert("red".to_string(), "#d70000".to_string());
palette.insert("blue".to_string(), "17".to_string());
palette.insert("green".to_string(), "green".to_string());
assert_eq!(
parse_color_string("mustard", Some(&palette)),
Some(Color::Rgb(175, 135, 0))
);
assert_eq!(
parse_color_string("sky-blue", Some(&palette)),
Some(Color::Fixed(51))
);
// Test overriding predefined colors
assert_eq!(
parse_color_string("red", Some(&palette)),
Some(Color::Rgb(215, 0, 0))
);
assert_eq!(
parse_color_string("blue", Some(&palette)),
Some(Color::Fixed(17))
);
// Test overriding a predefined color with itself
assert_eq!(
parse_color_string("green", Some(&palette)),
Some(Color::Green)
)
}
#[test]
fn table_get_palette() {
// Test retrieving color palette by name
let mut palette1 = Palette::new();
palette1.insert("test-color".to_string(), "123".to_string());
let mut palette2 = Palette::new();
palette2.insert("test-color".to_string(), "#ABCDEF".to_string());
let mut palettes = HashMap::<String, Palette>::new();
palettes.insert("palette1".to_string(), palette1);
palettes.insert("palette2".to_string(), palette2);
assert_eq!(
get_palette(&palettes, Some("palette1"))
.unwrap()
.get("test-color")
.unwrap(),
"123"
);
assert_eq!(
get_palette(&palettes, Some("palette2"))
.unwrap()
.get("test-color")
.unwrap(),
"#ABCDEF"
);
// Test retrieving nonexistent color palette
assert!(get_palette(&palettes, Some("palette3")).is_none());
// Test default behavior
assert!(get_palette(&palettes, None).is_none());
}
} }

View File

@ -1,4 +1,5 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Clone, Serialize, Deserialize, Debug)] #[derive(Clone, Serialize, Deserialize, Debug)]
#[cfg_attr( #[cfg_attr(
@ -16,8 +17,13 @@ pub struct StarshipRootConfig {
pub scan_timeout: u64, pub scan_timeout: u64,
pub command_timeout: u64, pub command_timeout: u64,
pub add_newline: bool, pub add_newline: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub palette: Option<String>,
pub palettes: HashMap<String, Palette>,
} }
pub type Palette = HashMap<String, String>;
// List of default prompt order // List of default prompt order
// NOTE: If this const value is changed then Default prompt order subheading inside // NOTE: If this const value is changed then Default prompt order subheading inside
// prompt heading of config docs needs to be updated according to changes made here. // prompt heading of config docs needs to be updated according to changes made here.
@ -114,6 +120,8 @@ impl Default for StarshipRootConfig {
scan_timeout: 30, scan_timeout: 30,
command_timeout: 500, command_timeout: 500,
add_newline: true, add_newline: true,
palette: None,
palettes: HashMap::default(),
} }
} }
} }

View File

@ -245,7 +245,7 @@ impl<'a> StringFormatter<'a> {
style_variables: &'a StyleVariableMapType<'a>, style_variables: &'a StyleVariableMapType<'a>,
context: Option<&Context>, context: Option<&Context>,
) -> Result<Vec<Segment>, StringFormatterError> { ) -> Result<Vec<Segment>, StringFormatterError> {
let style = parse_style(textgroup.style, style_variables); let style = parse_style(textgroup.style, style_variables, context);
parse_format( parse_format(
textgroup.format, textgroup.format,
style.transpose()?, style.transpose()?,
@ -258,6 +258,7 @@ impl<'a> StringFormatter<'a> {
fn parse_style<'a>( fn parse_style<'a>(
style: Vec<StyleElement>, style: Vec<StyleElement>,
variables: &'a StyleVariableMapType<'a>, variables: &'a StyleVariableMapType<'a>,
context: Option<&Context>,
) -> Option<Result<Style, StringFormatterError>> { ) -> Option<Result<Style, StringFormatterError>> {
let style_strings = style let style_strings = style
.into_iter() .into_iter()
@ -276,7 +277,7 @@ impl<'a> StringFormatter<'a> {
.map(|style_strings| { .map(|style_strings| {
let style_string: String = let style_string: String =
style_strings.iter().flat_map(|s| s.chars()).collect(); style_strings.iter().flat_map(|s| s.chars()).collect();
parse_style_string(&style_string) parse_style_string(&style_string, context)
}) })
.transpose() .transpose()
} }

View File

@ -14,7 +14,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
return None; return None;
} }
let style = parse_style_string(config.style); let style = parse_style_string(config.style, Some(context));
module.set_segments(vec![Segment::fill(style, config.symbol)]); module.set_segments(vec![Segment::fill(style, config.symbol)]);