on master help sign in
Load theme from toml file.
a65395dBlaž Hrastnik
Cargo.lock+2
@@ -565,8 +565,10 @@ dependencies = [
565 "helix-core",
565 "helix-core",
566 "helix-lsp",
566 "helix-lsp",
567 "once_cell",
567 "once_cell",
568+ "serde",
568 "slotmap",
569 "slotmap",
569 "smol",
570 "smol",
571+ "toml",
570 "tui",
572 "tui",
571 "url",
573 "url",
572]
574]
helix-view/Cargo.toml+3
@@ -26,3 +26,6 @@ smol = "1"
26futures-util = "0.3"
26futures-util = "0.3"
27
27
28slotmap = "1"
28slotmap = "1"
29+
30+serde = { version = "1.0", features = ["derive"] }
31+toml = "0.5"
helix-view/src/editor.rs+4−1
@@ -24,7 +24,10 @@ pub enum Action {
24
24
25impl Editor {
25impl Editor {
26 pub fn new(executor: &'static smol::Executor<'static>, mut area: tui::layout::Rect) -> Self {
26 pub fn new(executor: &'static smol::Executor<'static>, mut area: tui::layout::Rect) -> Self {
27− let theme = Theme::default();
27+ // TODO: load from config dir
28+ let toml = include_str!("../../theme.toml");
29+ let theme: Theme = toml::from_str(&toml).expect("failed to parse theme.toml");
30+
28 let language_servers = helix_lsp::Registry::new();
31 let language_servers = helix_lsp::Registry::new();
29
32
30 // HAXX: offset the render area height by 1 to account for prompt/commandline
33 // HAXX: offset the render area height by 1 to account for prompt/commandline
helix-view/src/theme.rs+76−55
@@ -1,6 +1,8 @@
1−use helix_core::hashmap;
2use std::collections::HashMap;
1use std::collections::HashMap;
3
2
3+use serde::{Deserialize, Deserializer};
4+use toml::Value;
5+
4#[cfg(feature = "term")]
6#[cfg(feature = "term")]
5pub use tui::style::{Color, Style};
7pub use tui::style::{Color, Style};
6
8
@@ -83,69 +85,88 @@ pub use tui::style::{Color, Style};
83// }
85// }
84
86
85/// Color theme for syntax highlighting.
87/// Color theme for syntax highlighting.
88+#[derive(Debug)]
86pub struct Theme {
89pub struct Theme {
87 scopes: Vec<String>,
90 scopes: Vec<String>,
88− mapping: HashMap<&'static str, Style>,
91+ styles: HashMap<String, Style>,
92+}
93+
94+impl<'de> Deserialize<'de> for Theme {
95+ fn deserialize<D>(deserializer: D) -> Result<Theme, D::Error>
96+ where
97+ D: Deserializer<'de>,
98+ {
99+ let mut styles = HashMap::new();
100+
101+ if let Ok(colors) = HashMap::<String, Value>::deserialize(deserializer) {
102+ // scopes.reserve(colors.len());
103+ styles.reserve(colors.len());
104+ for (name, style_value) in colors {
105+ let mut style = Style::default();
106+ parse_style(&mut style, style_value);
107+ // scopes.push(name);
108+ styles.insert(name, style);
109+ }
110+ }
111+
112+ let scopes = styles.keys().map(ToString::to_string).collect();
113+ Ok(Theme { scopes, styles })
114+ }
115+}
116+
117+fn parse_style(style: &mut Style, value: Value) {
118+ if let Value::Table(entries) = value {
119+ for (name, value) in entries {
120+ match name.as_str() {
121+ "fg" => {
122+ if let Some(color) = parse_color(value) {
123+ *style = style.fg(color);
124+ }
125+ }
126+ "bg" => {
127+ if let Some(color) = parse_color(value) {
128+ *style = style.bg(color);
129+ }
130+ }
131+ _ => (),
132+ }
133+ }
134+ } else if let Some(color) = parse_color(value) {
135+ *style = style.fg(color);
136+ }
137+}
138+
139+fn hex_string_to_rgb(s: &str) -> Option<(u8, u8, u8)> {
140+ if s.starts_with("#") && s.len() >= 7 {
141+ if let (Ok(red), Ok(green), Ok(blue)) = (
142+ u8::from_str_radix(&s[1..3], 16),
143+ u8::from_str_radix(&s[3..5], 16),
144+ u8::from_str_radix(&s[5..7], 16),
145+ ) {
146+ Some((red, green, blue))
147+ } else {
148+ None
149+ }
150+ } else {
151+ None
152+ }
89}
153}
90
154
91−impl Default for Theme {
155+fn parse_color(value: Value) -> Option<Color> {
92− fn default() -> Self {
156+ if let Value::String(s) = value {
93− let mapping = hashmap! {
157+ if let Some((red, green, blue)) = hex_string_to_rgb(&s) {
94− "attribute" => Style::default().fg(Color::Rgb(219, 191, 239)), // lilac
158+ Some(Color::Rgb(red, green, blue))
95− "keyword" => Style::default().fg(Color::Rgb(236, 205, 186)), // almond
159+ } else {
96− "punctuation" => Style::default().fg(Color::Rgb(164, 160, 232)), // lavender
160+ None
97− "punctuation.delimiter" => Style::default().fg(Color::Rgb(164, 160, 232)), // lavender
98− "operator" => Style::default().fg(Color::Rgb(219, 191, 239)), // lilac
99− "property" => Style::default().fg(Color::Rgb(164, 160, 232)), // lavender
100− "variable.parameter" => Style::default().fg(Color::Rgb(164, 160, 232)), // lavender
101− // TODO distinguish type from type.builtin?
102− "type" => Style::default().fg(Color::Rgb(255, 255, 255)), // white
103− "type.builtin" => Style::default().fg(Color::Rgb(255, 255, 255)), // white
104− "constructor" => Style::default().fg(Color::Rgb(219, 191, 239)), // lilac
105− "function" => Style::default().fg(Color::Rgb(255, 255, 255)), // white
106− "function.macro" => Style::default().fg(Color::Rgb(219, 191, 239)), // lilac
107− "comment" => Style::default().fg(Color::Rgb(105, 124, 129)), // sirocco
108− "variable.builtin" => Style::default().fg(Color::Rgb(159, 242, 143)), // mint
109− "constant" => Style::default().fg(Color::Rgb(255, 255, 255)), // white
110− "constant.builtin" => Style::default().fg(Color::Rgb(255, 255, 255)), // white
111− "string" => Style::default().fg(Color::Rgb(204, 204, 204)), // silver
112− "escape" => Style::default().fg(Color::Rgb(239, 186, 93)), // honey
113− // used for lifetimes
114− "label" => Style::default().fg(Color::Rgb(239, 186, 93)), // honey
115−
116− // TODO: diferentiate number builtin
117− // TODO: diferentiate doc comment
118− // TODO: variable as lilac
119− // TODO: mod/use statements as white
120− // TODO: mod stuff as chamoise
121− // TODO: add "(scoped_identifier) @path" for std::mem::
122− //
123− // concat (ERROR) @syntax-error and "MISSING ;" selectors for errors
124−
125− "module" => Style::default().fg(Color::Rgb(255, 0, 0)), // white
126− "variable" => Style::default().fg(Color::Rgb(255, 0, 0)), // white
127− "function.builtin" => Style::default().fg(Color::Rgb(255, 0, 0)), // white
128−
129− "ui.background" => Style::default().bg(Color::Rgb(59, 34, 76)), // midnight
130− "ui.linenr" => Style::default().fg(Color::Rgb(90, 89, 119)), // comet
131− "ui.statusline" => Style::default().bg(Color::Rgb(40, 23, 51)), // revolver
132− "ui.popup" => Style::default().bg(Color::Rgb(40, 23, 51)), // revolver
133−
134− "warning" => Style::default().fg(Color::Rgb(255, 205, 28)),
135− "error" => Style::default().fg(Color::Rgb(244, 120, 104)),
136− "info" => Style::default().fg(Color::Rgb(111, 68, 240)),
137− "hint" => Style::default().fg(Color::Rgb(204, 204, 204)),
138− };
161+ }
139−
162+ } else {
140− let scopes = mapping.keys().map(ToString::to_string).collect();
163+ None
141−
142− Self { scopes, mapping }
143 }
164 }
144}
165}
145
166
146impl Theme {
167impl Theme {
147 pub fn get(&self, scope: &str) -> Style {
168 pub fn get(&self, scope: &str) -> Style {
148− self.mapping
169+ self.styles
149 .get(scope)
170 .get(scope)
150 .copied()
171 .copied()
151 .unwrap_or_else(|| Style::default().fg(Color::Rgb(0, 0, 255)))
172 .unwrap_or_else(|| Style::default().fg(Color::Rgb(0, 0, 255)))
theme.tomlnew file+44
@@ -0,0 +1,44 @@
1+"attribute" = "#dbbfef" # lilac
2+"keyword" = "#eccdba" # almond
3+"punctuation" = "#a4a0e8" # lavender
4+"punctuation.delimiter" = "#a4a0e8" # lavender
5+"operator" = "#dbbfef" # lilac
6+"property" = "#a4a0e8" # lavender
7+"variable.parameter" = "#a4a0e8" # lavender
8+# TODO distinguish type from type.builtin?
9+"type" = "#ffffff" # white
10+"type.builtin" = "#ffffff" # white
11+"constructor" = "#dbbfef" # lilac
12+"function" = "#ffffff" # white
13+"function.macro" = "#dbbfef" # lilac
14+"comment" = "#697C81" # sirocco
15+"variable.builtin" = "#9ff28f" # mint
16+"constant" = "#ffffff" # white
17+"constant.builtin" = "#ffffff" # white
18+"string" = "#cccccc" # silver
19+"escape" = "#efba5d" # honey
20+# used for lifetimes
21+"label" = "#efba5d" # honey
22+
23+# TODO: diferentiate number builtin
24+# TODO: diferentiate doc comment
25+# TODO: variable as lilac
26+# TODO: mod/use statements as white
27+# TODO: mod stuff as chamoise
28+# TODO: add "(scoped_identifier) @path" for std::mem::
29+#
30+# concat (ERROR) @syntax-error and "MISSING ;" selectors for errors
31+
32+"module" = "#ff0000"
33+"variable" = "#ff0000"
34+"function.builtin" = "#ff0000"
35+
36+"ui.background" = { bg = "#3b224c" } # midnight
37+"ui.linenr" = { fg = "#5a5977" } # comet
38+"ui.statusline" = { bg = "#281733" } # revolver
39+"ui.popup" = { bg = "#281733" } # revolver
40+
41+"warning" = "#ffcd1c"
42+"error" = "#f47868"
43+"info" = "#6F44F0"
44+"hint" = "#cccccc"