From 89d9a209089d087bd65bbb90cbfb5a41b80b37a9 Mon Sep 17 00:00:00 2001 From: Pascal Kuthe Date: Sat, 1 Oct 2022 02:15:25 +0200 Subject: [PATCH] make underline_style a seperate option Underline styles are mutally exclusive and overwrite each other. Therefore implementing as an modifier lead to incorrect behaviour when the underline style is overwritten. For backwards compatability the "underline" modified is retained (but deprecated). Instead the "underline_style" and "underline_color" optios should be used to style underlines. --- book/src/themes.md | 23 ++++++-- helix-tui/src/backend/crossterm.rs | 56 ++++++++----------- helix-tui/src/buffer.rs | 22 +++++--- helix-view/src/graphics.rs | 89 ++++++++++++++++++++++-------- helix-view/src/gutter.rs | 4 +- helix-view/src/theme.rs | 23 +++++++- runtime/themes/dark_plus.toml | 4 +- runtime/themes/onedark.toml | 8 +-- 8 files changed, 149 insertions(+), 80 deletions(-) diff --git a/book/src/themes.md b/book/src/themes.md index 32ff2498f1ac4..450b3364958fa 100644 --- a/book/src/themes.md +++ b/book/src/themes.md @@ -13,10 +13,10 @@ The default theme.toml can be found [here](https://github.com/helix-editor/helix Each line in the theme file is specified as below: ```toml -key = { fg = "#ffffff", bg = "#000000", underline = "#ff0000", modifiers = ["bold", "italic", "undercurled"] } +key = { fg = "#ffffff", bg = "#000000", underline_color = "#ff0000", underline_style = "curl", modifiers = ["bold", "italic"] } ``` -where `key` represents what you want to style, `fg` specifies the foreground color, `bg` the background color, `underline` the underline color (only meaningful if an underline modifier is enabled), and `modifiers` is a list of style modifiers. `bg`, `underline` and `modifiers` can be omitted to defer to the defaults. +where `key` represents what you want to style, `fg` specifies the foreground color, `bg` the background color, `underline_style` the underline style, `underline_color` the underline color (only meaningful if an underline style is enabled), and `modifiers` is a list of style modifiers. `bg`, `underline` and `modifiers` can be omitted to defer to the defaults. To specify only the foreground color: @@ -83,16 +83,27 @@ Less common modifiers might not be supported by your terminal emulator. | `dim` | | `italic` | | `underlined` | -| `undercurled` | -| `underdashed` | -| `underdotted` | -| `double-underlined` | | `slow_blink` | | `rapid_blink` | | `reversed` | | `hidden` | | `crossed_out` | +### Underline Style + +One of the following values may be used as an `underline_styles`. + +Some styles might not be supported by your terminal emulator. + +| Modifier | +| --- | +| `line` | +| `curl` | +| `dashed` | +| `dot` | +| `double-line` | + + ### Scopes The following is a list of scopes available to use for styling. diff --git a/helix-tui/src/backend/crossterm.rs b/helix-tui/src/backend/crossterm.rs index 3a50074ede122..3e6dc5f59f4b6 100644 --- a/helix-tui/src/backend/crossterm.rs +++ b/helix-tui/src/backend/crossterm.rs @@ -8,7 +8,7 @@ use crossterm::{ }, terminal::{self, Clear, ClearType}, }; -use helix_view::graphics::{Color, CursorKind, Modifier, Rect}; +use helix_view::graphics::{Color, CursorKind, Modifier, Rect, UnderlineStyle}; use std::io::{self, Write}; fn vte_version() -> Option { @@ -80,7 +80,8 @@ where { let mut fg = Color::Reset; let mut bg = Color::Reset; - let mut underline = Color::Reset; + let mut underline_color = Color::Reset; + let mut underline_style = UnderlineStyle::Reset; let mut modifier = Modifier::empty(); let mut last_pos: Option<(u16, u16)> = None; for (x, y, cell) in content { @@ -94,7 +95,7 @@ where from: modifier, to: cell.modifier, }; - diff.queue(&mut self.buffer, self.capabilities)?; + diff.queue(&mut self.buffer)?; modifier = cell.modifier; } if cell.fg != fg { @@ -107,10 +108,24 @@ where map_error(queue!(self.buffer, SetBackgroundColor(color)))?; bg = cell.bg; } - if cell.underline != underline { - let color = CColor::from(cell.underline); + if cell.underline_color != underline_color { + let color = CColor::from(cell.underline_color); map_error(queue!(self.buffer, SetUnderlineColor(color)))?; - underline = cell.underline; + underline_color = cell.underline_color; + } + + let mut new_underline_style = cell.underline_style; + if !self.capabilities.has_extended_underlines { + match new_underline_style { + UnderlineStyle::Reset => (), + _ => new_underline_style = UnderlineStyle::Line, + } + } + + if new_underline_style != underline_style { + let attr = CAttribute::from(cell.underline_style); + map_error(queue!(self.buffer, SetAttribute(attr)))?; + underline_style = new_underline_style; } map_error(queue!(self.buffer, Print(&cell.symbol)))?; @@ -118,6 +133,7 @@ where map_error(queue!( self.buffer, + SetUnderlineColor(CColor::Reset), SetForegroundColor(CColor::Reset), SetBackgroundColor(CColor::Reset), SetAttribute(CAttribute::Reset) @@ -174,7 +190,7 @@ struct ModifierDiff { } impl ModifierDiff { - fn queue(&self, mut w: W, caps: Capabilities) -> io::Result<()> + fn queue(&self, mut w: W) -> io::Result<()> where W: io::Write, { @@ -192,9 +208,6 @@ impl ModifierDiff { if removed.contains(Modifier::ITALIC) { map_error(queue!(w, SetAttribute(CAttribute::NoItalic)))?; } - if removed.intersects(Modifier::ANY_UNDERLINE) { - map_error(queue!(w, SetAttribute(CAttribute::NoUnderline)))?; - } if removed.contains(Modifier::DIM) { map_error(queue!(w, SetAttribute(CAttribute::NormalIntensity)))?; } @@ -205,14 +218,6 @@ impl ModifierDiff { map_error(queue!(w, SetAttribute(CAttribute::NoBlink)))?; } - let queue_styled_underline = |styled_underline, w: &mut W| -> io::Result<()> { - let underline = match caps.has_extended_underlines { - true => styled_underline, - false => CAttribute::Underlined, - }; - map_error(queue!(w, SetAttribute(underline))) - }; - let added = self.to - self.from; if added.contains(Modifier::REVERSED) { map_error(queue!(w, SetAttribute(CAttribute::Reverse)))?; @@ -223,21 +228,6 @@ impl ModifierDiff { if added.contains(Modifier::ITALIC) { map_error(queue!(w, SetAttribute(CAttribute::Italic)))?; } - if added.contains(Modifier::UNDERLINED) { - map_error(queue!(w, SetAttribute(CAttribute::Underlined)))?; - } - if added.contains(Modifier::UNDERCURLED) { - queue_styled_underline(CAttribute::Undercurled, &mut w)?; - } - if added.contains(Modifier::UNDERDOTTED) { - queue_styled_underline(CAttribute::Underdotted, &mut w)?; - } - if added.contains(Modifier::UNDERDASHED) { - queue_styled_underline(CAttribute::Underdashed, &mut w)?; - } - if added.contains(Modifier::DOUBLE_UNDERLINED) { - queue_styled_underline(CAttribute::DoubleUnderlined, &mut w)?; - } if added.contains(Modifier::DIM) { map_error(queue!(w, SetAttribute(CAttribute::Dim)))?; } diff --git a/helix-tui/src/buffer.rs b/helix-tui/src/buffer.rs index 3036608d5e5cc..a3169ed4690eb 100644 --- a/helix-tui/src/buffer.rs +++ b/helix-tui/src/buffer.rs @@ -3,7 +3,7 @@ use helix_core::unicode::width::UnicodeWidthStr; use std::cmp::min; use unicode_segmentation::UnicodeSegmentation; -use helix_view::graphics::{Color, Modifier, Rect, Style}; +use helix_view::graphics::{Color, Modifier, Rect, Style, UnderlineStyle}; /// A buffer cell #[derive(Debug, Clone, PartialEq)] @@ -11,7 +11,8 @@ pub struct Cell { pub symbol: String, pub fg: Color, pub bg: Color, - pub underline: Color, + pub underline_color: Color, + pub underline_style: UnderlineStyle, pub modifier: Modifier, } @@ -45,9 +46,13 @@ impl Cell { if let Some(c) = style.bg { self.bg = c; } - if let Some(c) = style.underline { - self.underline = c; + if let Some(c) = style.underline_color { + self.underline_color = c; } + if let Some(style) = style.underline_style { + self.underline_style = style; + } + self.modifier.insert(style.add_modifier); self.modifier.remove(style.sub_modifier); self @@ -57,7 +62,8 @@ impl Cell { Style::default() .fg(self.fg) .bg(self.bg) - .underline(self.underline) + .underline_color(self.underline_color) + .underline_style(self.underline_style) .add_modifier(self.modifier) } @@ -66,7 +72,8 @@ impl Cell { self.symbol.push(' '); self.fg = Color::Reset; self.bg = Color::Reset; - self.underline = Color::Reset; + self.underline_color = Color::Reset; + self.underline_style = UnderlineStyle::Reset; self.modifier = Modifier::empty(); } } @@ -77,7 +84,8 @@ impl Default for Cell { symbol: " ".into(), fg: Color::Reset, bg: Color::Reset, - underline: Color::Reset, + underline_color: Color::Reset, + underline_style: UnderlineStyle::Reset, modifier: Modifier::empty(), } } diff --git a/helix-view/src/graphics.rs b/helix-view/src/graphics.rs index 6c854fd0629e1..2c59ff9f78a4e 100644 --- a/helix-view/src/graphics.rs +++ b/helix-view/src/graphics.rs @@ -315,6 +315,44 @@ impl From for crossterm::style::Color { } } +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum UnderlineStyle { + Reset, + Line, + Curl, + Dotted, + Dashed, + DoubleLine, +} + +impl FromStr for UnderlineStyle { + type Err = &'static str; + + fn from_str(modifier: &str) -> Result { + match modifier { + "line" => Ok(Self::Line), + "curl" => Ok(Self::Curl), + "dotted" => Ok(Self::Dotted), + "dashed" => Ok(Self::Dashed), + "double_line" => Ok(Self::DoubleLine), + _ => Err("Invalid underline style"), + } + } +} + +impl From for crossterm::style::Attribute { + fn from(style: UnderlineStyle) -> Self { + match style { + UnderlineStyle::Line => crossterm::style::Attribute::Underlined, + UnderlineStyle::Curl => crossterm::style::Attribute::Undercurled, + UnderlineStyle::Dotted => crossterm::style::Attribute::Underdotted, + UnderlineStyle::Dashed => crossterm::style::Attribute::Underdashed, + UnderlineStyle::DoubleLine => crossterm::style::Attribute::DoubleUnderlined, + UnderlineStyle::Reset => crossterm::style::Attribute::NoUnderline, + } + } +} + bitflags! { /// Modifier changes the way a piece of text is displayed. /// @@ -332,22 +370,11 @@ bitflags! { const BOLD = 0b0000_0000_0000_0001; const DIM = 0b0000_0000_0000_0010; const ITALIC = 0b0000_0000_0000_0100; - const UNDERLINED = 0b0000_0000_0000_1000; const SLOW_BLINK = 0b0000_0000_0001_0000; const RAPID_BLINK = 0b0000_0000_0010_0000; const REVERSED = 0b0000_0000_0100_0000; const HIDDEN = 0b0000_0000_1000_0000; const CROSSED_OUT = 0b0000_0001_0000_0000; - const UNDERCURLED = 0b0000_0010_0000_0000; - const UNDERDOTTED = 0b0000_0100_0000_0000; - const UNDERDASHED = 0b0000_1000_0000_0000; - const DOUBLE_UNDERLINED = 0b0001_0000_0000_0000; - - const ANY_UNDERLINE = Self::UNDERLINED.bits - | Self::UNDERCURLED.bits - | Self::UNDERDOTTED.bits - | Self::UNDERDASHED.bits - | Self::DOUBLE_UNDERLINED.bits; } } @@ -359,16 +386,11 @@ impl FromStr for Modifier { "bold" => Ok(Self::BOLD), "dim" => Ok(Self::DIM), "italic" => Ok(Self::ITALIC), - "underlined" => Ok(Self::UNDERLINED), "slow_blink" => Ok(Self::SLOW_BLINK), "rapid_blink" => Ok(Self::RAPID_BLINK), "reversed" => Ok(Self::REVERSED), "hidden" => Ok(Self::HIDDEN), "crossed_out" => Ok(Self::CROSSED_OUT), - "undercurled" => Ok(Self::UNDERCURLED), - "underdotted" => Ok(Self::UNDERDOTTED), - "underdashed" => Ok(Self::UNDERDASHED), - "double_underlined" => Ok(Self::DOUBLE_UNDERLINED), _ => Err("Invalid modifier"), } } @@ -442,7 +464,8 @@ impl FromStr for Modifier { pub struct Style { pub fg: Option, pub bg: Option, - pub underline: Option, + pub underline_color: Option, + pub underline_style: Option, pub add_modifier: Modifier, pub sub_modifier: Modifier, } @@ -452,7 +475,8 @@ impl Default for Style { Style { fg: None, bg: None, - underline: None, + underline_color: None, + underline_style: None, add_modifier: Modifier::empty(), sub_modifier: Modifier::empty(), } @@ -465,7 +489,8 @@ impl Style { Style { fg: Some(Color::Reset), bg: Some(Color::Reset), - underline: Some(Color::Reset), + underline_color: None, + underline_style: None, add_modifier: Modifier::empty(), sub_modifier: Modifier::all(), } @@ -507,12 +532,27 @@ impl Style { /// /// ```rust /// # use helix_view::graphics::{Color, Style}; - /// let style = Style::default().underline(Color::Blue); - /// let diff = Style::default().underline(Color::Red); + /// let style = Style::default().underline_color(Color::Blue); + /// let diff = Style::default().underline_color(Color::Red); /// assert_eq!(style.patch(diff), Style::default().underline(Color::Red)); /// ``` - pub fn underline(mut self, color: Color) -> Style { - self.underline = Some(color); + pub fn underline_color(mut self, color: Color) -> Style { + self.underline_color = Some(color); + self + } + + /// Changes the underline style. + /// + /// ## Examples + /// + /// ```rust + /// # use helix_view::graphics::{UnderlineStyle, Style}; + /// let style = Style::default().underline_style(UnderlineStyle::Line); + /// let diff = Style::default().underline_style(UnderlineStyle::Curl); + /// assert_eq!(style.patch(diff), Style::default().underline_style(UnderlineStyle::Curl)); + /// ``` + pub fn underline_style(mut self, style: UnderlineStyle) -> Style { + self.underline_style = Some(style); self } @@ -572,7 +612,8 @@ impl Style { pub fn patch(mut self, other: Style) -> Style { self.fg = other.fg.or(self.fg); self.bg = other.bg.or(self.bg); - self.underline = other.underline.or(self.underline); + self.underline_color = other.underline_color.or(self.underline_color); + self.underline_style = other.underline_style.or(self.underline_style); self.add_modifier.remove(other.sub_modifier); self.add_modifier.insert(other.add_modifier); diff --git a/helix-view/src/gutter.rs b/helix-view/src/gutter.rs index ab0e298661775..2c207d2768d4d 100644 --- a/helix-view/src/gutter.rs +++ b/helix-view/src/gutter.rs @@ -1,7 +1,7 @@ use std::fmt::Write; use crate::{ - graphics::{Color, Modifier, Style}, + graphics::{Color, Style, UnderlineStyle}, Document, Editor, Theme, View, }; @@ -147,7 +147,7 @@ pub fn breakpoints<'doc>( .find(|breakpoint| breakpoint.line == line)?; let mut style = if breakpoint.condition.is_some() && breakpoint.log_message.is_some() { - error.add_modifier(Modifier::UNDERLINED) + error.underline_style(UnderlineStyle::Line) } else if breakpoint.condition.is_some() { error } else if breakpoint.log_message.is_some() { diff --git a/helix-view/src/theme.rs b/helix-view/src/theme.rs index 5ce1b2c579dfc..90185937c94ae 100644 --- a/helix-view/src/theme.rs +++ b/helix-view/src/theme.rs @@ -10,6 +10,7 @@ use once_cell::sync::Lazy; use serde::{Deserialize, Deserializer}; use toml::Value; +use crate::graphics::UnderlineStyle; pub use crate::graphics::{Color, Modifier, Style}; pub static DEFAULT_THEME: Lazy = Lazy::new(|| { @@ -263,20 +264,38 @@ impl ThemePalette { .ok_or(format!("Theme: invalid modifier: {}", value)) } + pub fn parse_underline_style(value: &Value) -> Result { + value + .as_str() + .and_then(|s| s.parse().ok()) + .ok_or(format!("Theme: invalid underline_style: {}", value)) + } + pub fn parse_style(&self, style: &mut Style, value: Value) -> Result<(), String> { if let Value::Table(entries) = value { for (name, value) in entries { match name.as_str() { "fg" => *style = style.fg(self.parse_color(value)?), "bg" => *style = style.bg(self.parse_color(value)?), - "underline" => *style = style.underline(self.parse_color(value)?), + "underline_color" => *style = style.underline_color(self.parse_color(value)?), + "underline_style" => { + warn!("found style"); + *style = style.underline_style(Self::parse_underline_style(&value)?) + } "modifiers" => { let modifiers = value .as_array() .ok_or("Theme: modifiers should be an array")?; for modifier in modifiers { - *style = style.add_modifier(Self::parse_modifier(modifier)?); + if modifier + .as_str() + .map_or(false, |modifier| modifier == "underlined") + { + *style = style.underline_style(UnderlineStyle::Line); + } else { + *style = style.add_modifier(Self::parse_modifier(modifier)?); + } } } _ => return Err(format!("Theme: invalid style attribute: {}", name)), diff --git a/runtime/themes/dark_plus.toml b/runtime/themes/dark_plus.toml index d1a5756efd39a..fa6b34ab49858 100644 --- a/runtime/themes/dark_plus.toml +++ b/runtime/themes/dark_plus.toml @@ -92,8 +92,8 @@ "info" = { fg = "light_blue" } "hint" = { fg = "light_gray3" } -"diagnostic.error" = {underline = "red", modifiers = ["undercurled"] } -"diagnostic" = {underline = "gold", modifiers = ["undercurled"] } +"diagnostic.error" = {underline_color = "red", underline_style = "curl"} +"diagnostic" = {underline_color = "gold", underline_style = "curl" } [palette] white = "#ffffff" diff --git a/runtime/themes/onedark.toml b/runtime/themes/onedark.toml index e2bc2c472d160..5f337a8d5d1f7 100644 --- a/runtime/themes/onedark.toml +++ b/runtime/themes/onedark.toml @@ -39,10 +39,10 @@ "diff.delta" = "gold" "diff.minus" = "red" -"diagnostic.info" = { underline = "blue", modifiers = ["undercurled"] } -"diagnostic.hint" = { underline = "green", modifiers = ["undercurled"] } -"diagnostic.warning" = { underline = "yellow", modifiers = ["undercurled"] } -"diagnostic.error" = { underline = "red", modifiers = ["undercurled"] } +"diagnostic.info" = { underline_color = "blue", underline_style = "curl" } +"diagnostic.hint" = { underline_color = "green", underline_style = "curl" } +"diagnostic.warning" = { underline_color = "yellow", underline_style = "curl" } +"diagnostic.error" = { underline_color = "red", underline_style = "curl" } "info" = { fg = "blue", modifiers = ["bold"] } "hint" = { fg = "green", modifiers = ["bold"] } "warning" = { fg = "yellow", modifiers = ["bold"] }