aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock2
-rw-r--r--helix-view/Cargo.toml3
-rw-r--r--helix-view/src/editor.rs5
-rw-r--r--helix-view/src/theme.rs131
-rw-r--r--theme.toml44
5 files changed, 129 insertions, 56 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 40c2aa13..3f4d3b8c 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 938f35e5..af322424 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 08dd4d00..374eddfd 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 e51fab3e..f7731929 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<String>,
- mapping: HashMap<&'static str, Style>,
+ styles: HashMap<String, Style>,
+}
+
+impl<'de> Deserialize<'de> for Theme {
+ fn deserialize<D>(deserializer: D) -> Result<Theme, D::Error>
+ where
+ D: Deserializer<'de>,
+ {
+ let mut styles = HashMap::new();
+
+ if let Ok(colors) = HashMap::<String, Value>::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<Color> {
+ 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 00000000..32ceb94b
--- /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"