diff --git a/Cargo.lock b/Cargo.lock index d93c8c7c..3d556388 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -355,6 +355,7 @@ dependencies = [ "helix-core", "helix-lsp", "helix-tui", + "log", "once_cell", "serde", "slotmap", diff --git a/book/src/configuration.md b/book/src/configuration.md index a025a48b..4528080b 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -1 +1,87 @@ # Configuration + +## Theme + +Use a custom theme by placing a theme.toml in your config directory (i.e ~/.config/helix/theme.toml). The default theme.toml can be found [here](https://github.com/helix-editor/helix/blob/master/theme.toml), and user submitted themes [here](https://github.com/helix-editor/helix/blob/master/contrib/themes). + +Styles in theme.toml are specified of in the form: + +```toml +key = { fg = "#ffffff", bg = "#000000", modifiers = ["bold", "italic"] } +``` + +where `name` represents what you want to style, `fg` specifies the foreground color, `bg` the background color, and `modifiers` is a list of style modifiers. `bg` and `modifiers` can be omitted to defer to the defaults. + +To specify only the foreground color: + +```toml +key = "#ffffff" +``` + +if the key contains a dot `'.'`, it must be quoted to prevent it being parsed as a [dotted key](https://toml.io/en/v1.0.0#keys). + +```toml +"key.key" = "#ffffff" +``` + +Possible modifiers: + +| modifier | +| --- | +| bold | +| dim | +| italic | +| underlined | +| slow\_blink | +| rapid\_blink | +| reversed | +| hidden | +| crossed\_out | + +Possible keys: + +| key | notes | +| --- | --- | +| attribute | | +| keyword | | +| keyword.directive | preprocessor directives (\#if in C) | +| namespace | | +| punctuation | | +| punctuation.delimiter | | +| operator | | +| special | | +| property | | +| variable | | +| variable.parameter | | +| type | | +| type.builtin | | +| constructor | | +| function | | +| function.macro | | +| function.builtin | | +| comment | | +| variable.builtin | | +| constant | | +| constant.builtin | | +| string | | +| number | | +| escape | escaped characters | +| label | used for lifetimes | +| module | | +| ui.background | | +| ui.linenr | | +| ui.statusline | | +| ui.popup | | +| ui.window | | +| ui.help | | +| ui.text | | +| ui.text.focus | | +| ui.menu.selected | | +| warning | LSP warning | +| error | LSP error | +| info | LSP info | +| hint | LSP hint | + +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. diff --git a/contrib/themes/README.md b/contrib/themes/README.md new file mode 100644 index 00000000..1c9c5ae9 --- /dev/null +++ b/contrib/themes/README.md @@ -0,0 +1,9 @@ +# User submitted themes + +If you submit a theme, please include a comment at the top with your name and email address: + +```toml +# Author : Name +``` + +We have a preview page for themes on our [wiki](https://github.com/helix-editor/helix/wiki/Themes)! diff --git a/contrib/themes/ingrid.toml b/contrib/themes/ingrid.toml new file mode 100644 index 00000000..60377b45 --- /dev/null +++ b/contrib/themes/ingrid.toml @@ -0,0 +1,46 @@ +# Author : Ingrid Rebecca Abraham + +"attribute" = "#839A53" +"keyword" = { fg = "#D74E50", modifiers = ["bold"] } +"keyword.directive" = "#6F873E" +"namespace" = "#839A53" +"punctuation" = "#C97270" +"punctuation.delimiter" = "#C97270" +"operator" = { fg = "#D74E50", modifiers = ["bold"] } +"special" = "#D68482" +"property" = "#89BEB7" +"variable" = "#A6B6CE" +"variable.parameter" = "#89BEB7" +"type" = { fg = "#A6B6CE", modifiers = ["bold"] } +"type.builtin" = "#839A53" +"constructor" = { fg = "#839A53", modifiers = ["bold"] } +"function" = { fg = "#89BEB7", modifiers = ["bold"] } +"function.macro" = { fg = "#D4A520", modifiers = ["bold"] } +"function.builtin" = "#89BEB7" +"comment" = "#A6B6CE" +"variable.builtin" = "#D4A520" +"constant" = "#D4A520" +"constant.builtin" = "#D4A520" +"string" = "#D74E50" +"number" = "#D74E50" +"escape" = { fg = "#D74E50", modifiers = ["bold"] } +"label" = "#D68482" + +"module" = "#839A53" + +"ui.background" = { bg = "#FFFCFD" } +"ui.linenr" = { fg = "#bbbbbb" } +"ui.statusline" = { bg = "#F3EAE9" } +"ui.popup" = { bg = "#F3EAE9" } +"ui.window" = { bg = "#D8B8B3" } +"ui.help" = { bg = "#D8B8B3", fg = "#250E07" } + +"ui.text" = { fg = "#7B91B3" } +"ui.text.focus" = { fg = "#250E07", modifiers= ["bold"] } + +"ui.menu.selected" = { fg = "#D74E50", bg = "#F3EAE9" } + +"warning" = "#D4A520" +"error" = "#D74E50" +"info" = "#839A53" +"hint" = "#A6B6CE" diff --git a/helix-view/Cargo.toml b/helix-view/Cargo.toml index 0d28c82d..b2d1a594 100644 --- a/helix-view/Cargo.toml +++ b/helix-view/Cargo.toml @@ -28,3 +28,4 @@ slotmap = "1" serde = { version = "1.0", features = ["derive"] } toml = "0.5" +log = "~0.4" diff --git a/helix-view/src/theme.rs b/helix-view/src/theme.rs index 8b695898..efb6a1af 100644 --- a/helix-view/src/theme.rs +++ b/helix-view/src/theme.rs @@ -1,10 +1,11 @@ use std::collections::HashMap; +use log::warn; use serde::{Deserialize, Deserializer}; use toml::Value; #[cfg(feature = "term")] -pub use tui::style::{Color, Style}; +pub use tui::style::{Color, Modifier, Style}; // #[derive(Clone, Copy, PartialEq, Eq, Default, Hash)] // pub struct Color { @@ -115,6 +116,7 @@ impl<'de> Deserialize<'de> for Theme { } fn parse_style(style: &mut Style, value: Value) { + //TODO: alert user of parsing failures if let Value::Table(entries) = value { for (name, value) in entries { match name.as_str() { @@ -128,6 +130,13 @@ fn parse_style(style: &mut Style, value: Value) { *style = style.bg(color); } } + "modifiers" => { + if let Value::Array(arr) = value { + for modifier in arr.iter().filter_map(parse_modifier) { + *style = style.add_modifier(modifier); + } + } + } _ => (), } } @@ -157,9 +166,34 @@ fn parse_color(value: Value) -> Option { if let Some((red, green, blue)) = hex_string_to_rgb(&s) { Some(Color::Rgb(red, green, blue)) } else { + warn!("malformed hexcode in theme: {}", s); None } } else { + warn!("unrecognized value in theme: {}", value); + None + } +} + +fn parse_modifier(value: &Value) -> Option { + if let Value::String(s) = value { + match s.as_str() { + "bold" => Some(Modifier::BOLD), + "dim" => Some(Modifier::DIM), + "italic" => Some(Modifier::ITALIC), + "underlined" => Some(Modifier::UNDERLINED), + "slow_blink" => Some(Modifier::SLOW_BLINK), + "rapid_blink" => Some(Modifier::RAPID_BLINK), + "reversed" => Some(Modifier::REVERSED), + "hidden" => Some(Modifier::HIDDEN), + "crossed_out" => Some(Modifier::CROSSED_OUT), + _ => { + warn!("unrecognized modifier in theme: {}", s); + None + } + } + } else { + warn!("unrecognized modifier in theme: {}", value); None } } @@ -177,3 +211,39 @@ impl Theme { &self.scopes } } + +#[test] +fn test_parse_style_string() { + let fg = Value::String("#ffffff".to_string()); + + let mut style = Style::default(); + parse_style(&mut style, fg); + + assert_eq!(style, Style::default().fg(Color::Rgb(255, 255, 255))); +} + +#[test] +fn test_parse_style_table() { + let table = toml::toml! { + "keyword" = { + fg = "#ffffff", + bg = "#000000", + modifiers = ["bold"], + } + }; + + let mut style = Style::default(); + if let Value::Table(entries) = table { + for (_name, value) in entries { + parse_style(&mut style, value); + } + } + + assert_eq!( + style, + Style::default() + .fg(Color::Rgb(255, 255, 255)) + .bg(Color::Rgb(0, 0, 0)) + .add_modifier(Modifier::BOLD) + ); +}