From a65395d94beb614bc17301601109042077e55e83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Thu, 25 Mar 2021 16:42:14 +0900 Subject: [PATCH] Load theme from toml file. --- Cargo.lock | 2 + helix-view/Cargo.toml | 3 + helix-view/src/editor.rs | 5 +- helix-view/src/theme.rs | 131 +++++++++++++++++++++++---------------- theme.toml | 44 +++++++++++++ 5 files changed, 129 insertions(+), 56 deletions(-) create mode 100644 theme.toml diff --git a/Cargo.lock b/Cargo.lock index 40c2aa13b..3f4d3b8cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -565,8 +565,10 @@ dependencies = [ "helix-core", "helix-lsp", "once_cell", + "serde", "slotmap", "smol", + "toml", "tui", "url", ] diff --git a/helix-view/Cargo.toml b/helix-view/Cargo.toml index 938f35e51..af3224244 100644 --- a/helix-view/Cargo.toml +++ b/helix-view/Cargo.toml @@ -26,3 +26,6 @@ smol = "1" futures-util = "0.3" slotmap = "1" + +serde = { version = "1.0", features = ["derive"] } +toml = "0.5" diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 08dd4d00d..374eddfd6 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -24,7 +24,10 @@ pub enum Action { impl Editor { pub fn new(executor: &'static smol::Executor<'static>, mut area: tui::layout::Rect) -> Self { - let theme = Theme::default(); + // TODO: load from config dir + let toml = include_str!("../../theme.toml"); + let theme: Theme = toml::from_str(&toml).expect("failed to parse theme.toml"); + let language_servers = helix_lsp::Registry::new(); // HAXX: offset the render area height by 1 to account for prompt/commandline diff --git a/helix-view/src/theme.rs b/helix-view/src/theme.rs index e51fab3ee..f77319296 100644 --- a/helix-view/src/theme.rs +++ b/helix-view/src/theme.rs @@ -1,6 +1,8 @@ -use helix_core::hashmap; use std::collections::HashMap; +use serde::{Deserialize, Deserializer}; +use toml::Value; + #[cfg(feature = "term")] pub use tui::style::{Color, Style}; @@ -83,69 +85,88 @@ pub use tui::style::{Color, Style}; // } /// Color theme for syntax highlighting. +#[derive(Debug)] pub struct Theme { scopes: Vec, - mapping: HashMap<&'static str, Style>, + styles: HashMap, +} + +impl<'de> Deserialize<'de> for Theme { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let mut styles = HashMap::new(); + + if let Ok(colors) = HashMap::::deserialize(deserializer) { + // 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); + // scopes.push(name); + styles.insert(name, style); + } + } + + let scopes = styles.keys().map(ToString::to_string).collect(); + Ok(Theme { scopes, styles }) + } +} + +fn parse_style(style: &mut Style, value: Value) { + if let Value::Table(entries) = value { + for (name, value) in entries { + match name.as_str() { + "fg" => { + if let Some(color) = parse_color(value) { + *style = style.fg(color); + } + } + "bg" => { + if let Some(color) = parse_color(value) { + *style = style.bg(color); + } + } + _ => (), + } + } + } else if let Some(color) = parse_color(value) { + *style = style.fg(color); + } +} + +fn hex_string_to_rgb(s: &str) -> Option<(u8, u8, u8)> { + if s.starts_with("#") && s.len() >= 7 { + if let (Ok(red), Ok(green), Ok(blue)) = ( + u8::from_str_radix(&s[1..3], 16), + u8::from_str_radix(&s[3..5], 16), + u8::from_str_radix(&s[5..7], 16), + ) { + Some((red, green, blue)) + } else { + None + } + } else { + None + } } -impl Default for Theme { - fn default() -> Self { - let mapping = hashmap! { - "attribute" => Style::default().fg(Color::Rgb(219, 191, 239)), // lilac - "keyword" => Style::default().fg(Color::Rgb(236, 205, 186)), // almond - "punctuation" => Style::default().fg(Color::Rgb(164, 160, 232)), // lavender - "punctuation.delimiter" => Style::default().fg(Color::Rgb(164, 160, 232)), // lavender - "operator" => Style::default().fg(Color::Rgb(219, 191, 239)), // lilac - "property" => Style::default().fg(Color::Rgb(164, 160, 232)), // lavender - "variable.parameter" => Style::default().fg(Color::Rgb(164, 160, 232)), // lavender - // TODO distinguish type from type.builtin? - "type" => Style::default().fg(Color::Rgb(255, 255, 255)), // white - "type.builtin" => Style::default().fg(Color::Rgb(255, 255, 255)), // white - "constructor" => Style::default().fg(Color::Rgb(219, 191, 239)), // lilac - "function" => Style::default().fg(Color::Rgb(255, 255, 255)), // white - "function.macro" => Style::default().fg(Color::Rgb(219, 191, 239)), // lilac - "comment" => Style::default().fg(Color::Rgb(105, 124, 129)), // sirocco - "variable.builtin" => Style::default().fg(Color::Rgb(159, 242, 143)), // mint - "constant" => Style::default().fg(Color::Rgb(255, 255, 255)), // white - "constant.builtin" => Style::default().fg(Color::Rgb(255, 255, 255)), // white - "string" => Style::default().fg(Color::Rgb(204, 204, 204)), // silver - "escape" => Style::default().fg(Color::Rgb(239, 186, 93)), // honey - // used for lifetimes - "label" => Style::default().fg(Color::Rgb(239, 186, 93)), // honey - - // TODO: diferentiate number builtin - // TODO: diferentiate doc comment - // TODO: variable as lilac - // TODO: mod/use statements as white - // TODO: mod stuff as chamoise - // TODO: add "(scoped_identifier) @path" for std::mem:: - // - // concat (ERROR) @syntax-error and "MISSING ;" selectors for errors - - "module" => Style::default().fg(Color::Rgb(255, 0, 0)), // white - "variable" => Style::default().fg(Color::Rgb(255, 0, 0)), // white - "function.builtin" => Style::default().fg(Color::Rgb(255, 0, 0)), // white - - "ui.background" => Style::default().bg(Color::Rgb(59, 34, 76)), // midnight - "ui.linenr" => Style::default().fg(Color::Rgb(90, 89, 119)), // comet - "ui.statusline" => Style::default().bg(Color::Rgb(40, 23, 51)), // revolver - "ui.popup" => Style::default().bg(Color::Rgb(40, 23, 51)), // revolver - - "warning" => Style::default().fg(Color::Rgb(255, 205, 28)), - "error" => Style::default().fg(Color::Rgb(244, 120, 104)), - "info" => Style::default().fg(Color::Rgb(111, 68, 240)), - "hint" => Style::default().fg(Color::Rgb(204, 204, 204)), - }; - - let scopes = mapping.keys().map(ToString::to_string).collect(); - - Self { scopes, mapping } +fn parse_color(value: Value) -> Option { + if let Value::String(s) = value { + if let Some((red, green, blue)) = hex_string_to_rgb(&s) { + Some(Color::Rgb(red, green, blue)) + } else { + None + } + } else { + None } } impl Theme { pub fn get(&self, scope: &str) -> Style { - self.mapping + self.styles .get(scope) .copied() .unwrap_or_else(|| Style::default().fg(Color::Rgb(0, 0, 255))) diff --git a/theme.toml b/theme.toml new file mode 100644 index 000000000..32ceb94b6 --- /dev/null +++ b/theme.toml @@ -0,0 +1,44 @@ +"attribute" = "#dbbfef" # lilac +"keyword" = "#eccdba" # almond +"punctuation" = "#a4a0e8" # lavender +"punctuation.delimiter" = "#a4a0e8" # lavender +"operator" = "#dbbfef" # lilac +"property" = "#a4a0e8" # lavender +"variable.parameter" = "#a4a0e8" # lavender +# TODO distinguish type from type.builtin? +"type" = "#ffffff" # white +"type.builtin" = "#ffffff" # white +"constructor" = "#dbbfef" # lilac +"function" = "#ffffff" # white +"function.macro" = "#dbbfef" # lilac +"comment" = "#697C81" # sirocco +"variable.builtin" = "#9ff28f" # mint +"constant" = "#ffffff" # white +"constant.builtin" = "#ffffff" # white +"string" = "#cccccc" # silver +"escape" = "#efba5d" # honey +# used for lifetimes +"label" = "#efba5d" # honey + +# TODO: diferentiate number builtin +# TODO: diferentiate doc comment +# TODO: variable as lilac +# TODO: mod/use statements as white +# TODO: mod stuff as chamoise +# TODO: add "(scoped_identifier) @path" for std::mem:: +# +# concat (ERROR) @syntax-error and "MISSING ;" selectors for errors + +"module" = "#ff0000" +"variable" = "#ff0000" +"function.builtin" = "#ff0000" + +"ui.background" = { bg = "#3b224c" } # midnight +"ui.linenr" = { fg = "#5a5977" } # comet +"ui.statusline" = { bg = "#281733" } # revolver +"ui.popup" = { bg = "#281733" } # revolver + +"warning" = "#ffcd1c" +"error" = "#f47868" +"info" = "#6F44F0" +"hint" = "#cccccc"