summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakub Bartodziej2021-06-30 14:24:30 +0000
committerGitHub2021-06-30 14:24:30 +0000
commit79f096963c75d107cd1f415666fb21a9cfd3cbd6 (patch)
tree22f117f8e6c1a16a2d22a031335ad2dc650ab0cc
parent2a92dd8d4d3acbdc55e98217260346622c95250e (diff)
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>
-rw-r--r--book/src/themes.md18
-rw-r--r--helix-view/src/theme.rs52
-rw-r--r--runtime/themes/gruvbox.toml83
3 files changed, 143 insertions, 10 deletions
diff --git a/book/src/themes.md b/book/src/themes.md
index 2ece2491..d6ed78ba 100644
--- a/book/src/themes.md
+++ b/book/src/themes.md
@@ -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.
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.
diff --git a/helix-view/src/theme.rs b/helix-view/src/theme.rs
index ece4fe9a..756e34f6 100644
--- a/helix-view/src/theme.rs
+++ b/helix-view/src/theme.rs
@@ -102,12 +102,13 @@ impl<'de> Deserialize<'de> for Theme {
{
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());
styles.reserve(colors.len());
for (name, style_value) in colors {
let mut style = Style::default();
- parse_style(&mut style, style_value);
+ parse_style(&mut style, style_value, &palette);
// scopes.push(name);
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
if let Value::Table(entries) = value {
for (name, value) in entries {
match name.as_str() {
"fg" => {
- if let Some(color) = parse_color(value) {
+ if let Some(color) = parse_color(value, palette) {
*style = style.fg(color);
}
}
"bg" => {
- if let Some(color) = parse_color(value) {
+ if let Some(color) = parse_color(value, palette) {
*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);
}
}
@@ -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 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))
} else {
warn!("malformed hexcode in theme: {}", s);
@@ -226,7 +242,23 @@ fn test_parse_style_string() {
let fg = Value::String("#ffffff".to_string());
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)));
}
@@ -244,7 +276,7 @@ fn test_parse_style_table() {
let mut style = Style::default();
if let Value::Table(entries) = table {
for (_name, value) in entries {
- parse_style(&mut style, value);
+ parse_style(&mut style, value, &HashMap::default());
}
}
diff --git a/runtime/themes/gruvbox.toml b/runtime/themes/gruvbox.toml
new file mode 100644
index 00000000..b606568d
--- /dev/null
+++ b/runtime/themes/gruvbox.toml
@@ -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"