Color palettes (#393)

* Enable using color palettes in theme files.

* Add an example theme defined using a gruvbox color palette.

* Fix clippy error.

* Small style improvement.

* Add documentation for the features to themes.md.

* Update runtime/themes/gruvbox.toml

Fix the value of purple0.

Co-authored-by: DrZingo <DrZingo@users.noreply.github.com>

Co-authored-by: DrZingo <DrZingo@users.noreply.github.com>
pull/398/head
Jakub Bartodziej 3 years ago committed by GitHub
parent 2a92dd8d4d
commit 79f096963c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -99,3 +99,21 @@ Possible keys:
These keys match [tree-sitter scopes](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#theme). We half-follow the common scopes from [macromates language grammars](https://macromates.com/manual/en/language_grammars) with some differences. These keys match [tree-sitter scopes](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#theme). We half-follow the common scopes from [macromates language grammars](https://macromates.com/manual/en/language_grammars) with some differences.
For a given highlight produced, styling will be determined based on the longest matching theme key. So it's enough to provide function to highlight `function.macro` and `function.builtin` as well, but you can use more specific scopes to highlight specific cases differently. For a given highlight produced, styling will be determined based on the longest matching theme key. So it's enough to provide function to highlight `function.macro` and `function.builtin` as well, but you can use more specific scopes to highlight specific cases differently.
## Color palettes
You can define a palette of named colors, and refer to them from the
configuration values in your theme. To do this, add a table called
`palette` to your theme file:
```toml
ui.background = "white"
ui.text = "black"
[palette]
white = "#ffffff"
black = "#000000"
```
Remember that the `[palette]` table includes all keys after its header,
so you should define the palette after normal theme options.

@ -102,12 +102,13 @@ impl<'de> Deserialize<'de> for Theme {
{ {
let mut styles = HashMap::new(); let mut styles = HashMap::new();
if let Ok(colors) = HashMap::<String, Value>::deserialize(deserializer) { if let Ok(mut colors) = HashMap::<String, Value>::deserialize(deserializer) {
let palette = parse_palette(colors.remove("palette"));
// scopes.reserve(colors.len()); // scopes.reserve(colors.len());
styles.reserve(colors.len()); styles.reserve(colors.len());
for (name, style_value) in colors { for (name, style_value) in colors {
let mut style = Style::default(); let mut style = Style::default();
parse_style(&mut style, style_value); parse_style(&mut style, style_value, &palette);
// scopes.push(name); // scopes.push(name);
styles.insert(name, style); styles.insert(name, style);
} }
@ -118,18 +119,31 @@ impl<'de> Deserialize<'de> for Theme {
} }
} }
fn parse_style(style: &mut Style, value: Value) { fn parse_palette(value: Option<Value>) -> HashMap<String, Color> {
match value {
Some(Value::Table(entries)) => entries,
_ => return HashMap::default(),
}
.into_iter()
.filter_map(|(name, value)| {
let color = parse_color(value, &HashMap::default())?;
Some((name, color))
})
.collect()
}
fn parse_style(style: &mut Style, value: Value, palette: &HashMap<String, Color>) {
//TODO: alert user of parsing failures //TODO: alert user of parsing failures
if let Value::Table(entries) = value { if let Value::Table(entries) = value {
for (name, value) in entries { for (name, value) in entries {
match name.as_str() { match name.as_str() {
"fg" => { "fg" => {
if let Some(color) = parse_color(value) { if let Some(color) = parse_color(value, palette) {
*style = style.fg(color); *style = style.fg(color);
} }
} }
"bg" => { "bg" => {
if let Some(color) = parse_color(value) { if let Some(color) = parse_color(value, palette) {
*style = style.bg(color); *style = style.bg(color);
} }
} }
@ -143,7 +157,7 @@ fn parse_style(style: &mut Style, value: Value) {
_ => (), _ => (),
} }
} }
} else if let Some(color) = parse_color(value) { } else if let Some(color) = parse_color(value, palette) {
*style = style.fg(color); *style = style.fg(color);
} }
} }
@ -164,9 +178,11 @@ fn hex_string_to_rgb(s: &str) -> Option<(u8, u8, u8)> {
} }
} }
fn parse_color(value: Value) -> Option<Color> { fn parse_color(value: Value, palette: &HashMap<String, Color>) -> Option<Color> {
if let Value::String(s) = value { if let Value::String(s) = value {
if let Some((red, green, blue)) = hex_string_to_rgb(&s) { if let Some(color) = palette.get(&s) {
Some(*color)
} else if let Some((red, green, blue)) = hex_string_to_rgb(&s) {
Some(Color::Rgb(red, green, blue)) Some(Color::Rgb(red, green, blue))
} else { } else {
warn!("malformed hexcode in theme: {}", s); warn!("malformed hexcode in theme: {}", s);
@ -226,7 +242,23 @@ fn test_parse_style_string() {
let fg = Value::String("#ffffff".to_string()); let fg = Value::String("#ffffff".to_string());
let mut style = Style::default(); let mut style = Style::default();
parse_style(&mut style, fg); parse_style(&mut style, fg, &HashMap::default());
assert_eq!(style, Style::default().fg(Color::Rgb(255, 255, 255)));
}
#[test]
fn test_palette() {
let fg = Value::String("my_color".to_string());
let mut style = Style::default();
parse_style(
&mut style,
fg,
&vec![("my_color".to_string(), Color::Rgb(255, 255, 255))]
.into_iter()
.collect(),
);
assert_eq!(style, Style::default().fg(Color::Rgb(255, 255, 255))); assert_eq!(style, Style::default().fg(Color::Rgb(255, 255, 255)));
} }
@ -244,7 +276,7 @@ fn test_parse_style_table() {
let mut style = Style::default(); let mut style = Style::default();
if let Value::Table(entries) = table { if let Value::Table(entries) = table {
for (_name, value) in entries { for (_name, value) in entries {
parse_style(&mut style, value); parse_style(&mut style, value, &HashMap::default());
} }
} }

@ -0,0 +1,83 @@
# Author : Jakub Bartodziej <kubabartodziej@gmail.com>
# The theme uses the gruvbox dark palette with standard contrast: github.com/morhetz/gruvbox
"attribute" = "fg2"
"keyword" = { fg = "red1" }
"keyword.directive" = "red0"
"namespace" = "yellow0"
"punctuation" = "fg4"
"punctuation.delimiter" = "fg4"
"operator" = "orange0"
"special" = "purple0"
"property" = "fg2"
"variable" = "fg2"
"variable.builtin" = "fg3"
"variable.parameter" = "fg2"
"type" = "orange1"
"type.builtin" = "orange0"
"constructor" = "fg2"
"function" = "green0"
"function.macro" = "aqua1"
"function.builtin" = "yellow1"
"comment" = "gray1"
"constant" = { fg = "fg2", modifiers = ["bold"] }
"constant.builtin" = { fg = "fg1", modifiers = ["bold"] }
"string" = "green1"
"number" = "purple1"
"escape" = { fg = "fg2", modifiers = ["bold"] }
"label" = "aqua1"
"module" = "yellow1"
"warning" = "orange0"
"error" = "red0"
"info" = "purple0"
"hint" = "blue0"
"ui.background" = { bg = "bg0" }
"ui.linenr" = { fg = "fg3" }
"ui.linenr.selected" = { fg = "fg1", modifiers = ["bold"] }
"ui.statusline" = { fg = "fg2", bg = "bg2" }
"ui.statusline.inactive" = { fg = "fg3", bg = "bg4" }
"ui.popup" = { bg = "bg1" }
"ui.window" = { bg = "bg1" }
"ui.help" = { bg = "bg1", fg = "fg1" }
"ui.text" = { fg = "fg1" }
"ui.text.focus" = { fg = "fg1", modifiers = ["bold"] }
"ui.selection" = { bg = "bg3" }
"ui.cursor.primary" = { bg = "bg3" }
"ui.cursor.match" = { bg = "bg4" }
"ui.menu" = { fg = "fg1" }
"ui.menu.selected" = { fg = "fg3", bg = "bg3" }
"diagnostic" = { modifiers = ["underlined"] }
[palette]
bg0 = "#282828" # main background
bg1 = "#3c3836"
bg2 = "#504945"
bg3 = "#665c54"
bg4 = "#7c6f64"
fg0 = "#fbf1c7"
fg1 = "#ebdbb2" # main foreground
fg2 = "#d5c4a1"
fg3 = "#bdae93"
fg4 = "#a89984" # gray0
gray0 = "#a89984"
gray1 = "#928374"
red0 = "#cc241d" # neutral
red1 = "#fb4934" # bright
green0 = "#98971a"
green1 = "#b8bb26"
yellow0 = "#d79921"
yellow1 = "#fabd2f"
blue0 = "#458588"
blue1 = "#83a598"
purple0 = "#b16286"
purple1 = "#d3869b"
aqua0 = "#689d6a"
aqua1 = "#8ec07c"
orange0 = "#d65d0e"
orange1 = "#fe8019"
Loading…
Cancel
Save