Parse rainbow style array in themes

This change adds a field to the schema of themes which takes a
list of styles.

    rainbow = ["red", "orange", "yellow", { modifiers = ["reversed"] }]
    [palette]
    red = "#ff0000"
    orange = "#ffa500"
    yellow = "#fff000"

Normal style rules apply for each element in `rainbows`: you can
use definitions from the palette and the full fg/bg/modifiers
notation.

Themes written with `rainbow` keys are not backwards compatible.
Parsing errors will be generated for older versions of Helix
attempting to use themes with `rainbow` keys.

A default rainbow is provided with base16 colors.

This change is made with rainbow pair characters (parens, brackets, etc.)
in mind but it could also be used for other rainbow cosmetic elements
like rainbow indent-guides.
pull/6/head
Michael Davis 2 years ago committed by s0LA1337
parent 9bbc12d7b6
commit 1daf0c35c7

@ -89,6 +89,17 @@ Less common modifiers might not be supported by your terminal emulator.
| `hidden` | | `hidden` |
| `crossed_out` | | `crossed_out` |
### Rainbow
The `rainbow` key is used for rainbow highlight for matching brackets.
The key is a list of styles.
```toml
rainbow = ["#ff0000", "#ffa500", "#fff000", { fg = "#00ff00", modifiers = ["bold"] }]
```
Colors from the palette and modifiers may be used.
### Scopes ### Scopes
The following is a list of scopes available to use for styling. The following is a list of scopes available to use for styling.

@ -103,6 +103,7 @@ pub struct Theme {
// tree-sitter highlight styles are stored in a Vec to optimize lookups // tree-sitter highlight styles are stored in a Vec to optimize lookups
scopes: Vec<String>, scopes: Vec<String>,
highlights: Vec<Style>, highlights: Vec<Style>,
rainbow_length: usize,
} }
impl<'de> Deserialize<'de> for Theme { impl<'de> Deserialize<'de> for Theme {
@ -113,6 +114,7 @@ impl<'de> Deserialize<'de> for Theme {
let mut styles = HashMap::new(); let mut styles = HashMap::new();
let mut scopes = Vec::new(); let mut scopes = Vec::new();
let mut highlights = Vec::new(); let mut highlights = Vec::new();
let mut rainbow_length = 0;
if let Ok(mut colors) = HashMap::<String, Value>::deserialize(deserializer) { if let Ok(mut colors) = HashMap::<String, Value>::deserialize(deserializer) {
// TODO: alert user of parsing failures in editor // TODO: alert user of parsing failures in editor
@ -130,6 +132,26 @@ impl<'de> Deserialize<'de> for Theme {
scopes.reserve(colors.len()); scopes.reserve(colors.len());
highlights.reserve(colors.len()); highlights.reserve(colors.len());
for (i, style) in colors
.remove("rainbow")
.and_then(|value| match palette.parse_style_array(value) {
Ok(styles) => Some(styles),
Err(err) => {
warn!("{}", err);
None
}
})
.unwrap_or_else(Self::default_rainbow)
.iter()
.enumerate()
{
let name = format!("rainbow.{}", i);
styles.insert(name.clone(), *style);
scopes.push(name);
highlights.push(*style);
rainbow_length += 1;
}
for (name, style_value) in colors { for (name, style_value) in colors {
let mut style = Style::default(); let mut style = Style::default();
if let Err(err) = palette.parse_style(&mut style, style_value) { if let Err(err) = palette.parse_style(&mut style, style_value) {
@ -147,6 +169,7 @@ impl<'de> Deserialize<'de> for Theme {
scopes, scopes,
styles, styles,
highlights, highlights,
rainbow_length,
}) })
} }
} }
@ -185,6 +208,21 @@ impl Theme {
.all(|color| !matches!(color, Some(Color::Rgb(..)))) .all(|color| !matches!(color, Some(Color::Rgb(..))))
}) })
} }
pub fn rainbow_length(&self) -> usize {
self.rainbow_length
}
pub fn default_rainbow() -> Vec<Style> {
vec![
Style::default().fg(Color::Red),
Style::default().fg(Color::Yellow),
Style::default().fg(Color::Green),
Style::default().fg(Color::Blue),
Style::default().fg(Color::Cyan),
Style::default().fg(Color::Magenta),
]
}
} }
struct ThemePalette { struct ThemePalette {
@ -286,6 +324,24 @@ impl ThemePalette {
} }
Ok(()) Ok(())
} }
/// Parses a TOML array into a [`Vec`] of [`Style`]. If the value cannot be
/// parsed as an array or if any style in the array cannot be parsed then an
/// error is returned.
pub fn parse_style_array(&self, value: Value) -> Result<Vec<Style>, String> {
let mut styles = Vec::new();
for v in value
.as_array()
.ok_or_else(|| format!("Theme: could not parse value as an array: '{}'", value))?
{
let mut style = Style::default();
self.parse_style(&mut style, v.clone())?;
styles.push(style);
}
Ok(styles)
}
} }
impl TryFrom<Value> for ThemePalette { impl TryFrom<Value> for ThemePalette {
@ -362,4 +418,51 @@ mod tests {
.add_modifier(Modifier::BOLD) .add_modifier(Modifier::BOLD)
); );
} }
#[test]
fn test_parse_valid_style_array() {
let theme = toml::toml! {
rainbow = ["#ff0000", "#ffa500", "#fff000", { fg = "#00ff00", modifiers = ["bold"] }]
};
let palette = ThemePalette::default();
let rainbow = theme.as_table().unwrap().get("rainbow").unwrap();
let parse_result = palette.parse_style_array(rainbow.clone());
assert_eq!(
Ok(vec![
Style::default().fg(Color::Rgb(255, 0, 0)),
Style::default().fg(Color::Rgb(255, 165, 0)),
Style::default().fg(Color::Rgb(255, 240, 0)),
Style::default()
.fg(Color::Rgb(0, 255, 0))
.add_modifier(Modifier::BOLD),
]),
parse_result
)
}
#[test]
fn test_parse_invalid_style_array() {
let palette = ThemePalette::default();
let theme = toml::toml! { invalid_hex_code = ["#f00"] };
let invalid_hex_code = theme.as_table().unwrap().get("invalid_hex_code").unwrap();
let parse_result = palette.parse_style_array(invalid_hex_code.clone());
assert_eq!(
Err("Theme: malformed hexcode: #f00".to_string()),
parse_result
);
let theme = toml::toml! { not_an_array = { red = "#ff0000" } };
let not_an_array = theme.as_table().unwrap().get("not_an_array").unwrap();
let parse_result = palette.parse_style_array(not_an_array.clone());
assert_eq!(
Err("Theme: could not parse value as an array: 'red = \"#ff0000\"\n'".to_string()),
parse_result
)
}
} }

Loading…
Cancel
Save