From 999b45b28c157418c20a9a8cd9219db6ce0beac7 Mon Sep 17 00:00:00 2001 From: Gokul Soumya Date: Sat, 9 Jul 2022 03:35:06 +0530 Subject: [PATCH 001/183] Support different kinds of underline rendering Adds four new modifiers that can be used in themes: - undercurled - underdashed - underdotted - double-underline --- helix-tui/src/backend/crossterm.rs | 14 +++++++++++- helix-view/src/graphics.rs | 34 +++++++++++++++++++++--------- runtime/themes/onedark.toml | 2 +- 3 files changed, 38 insertions(+), 12 deletions(-) diff --git a/helix-tui/src/backend/crossterm.rs b/helix-tui/src/backend/crossterm.rs index eff098b3..252036f3 100644 --- a/helix-tui/src/backend/crossterm.rs +++ b/helix-tui/src/backend/crossterm.rs @@ -153,7 +153,7 @@ impl ModifierDiff { if removed.contains(Modifier::ITALIC) { map_error(queue!(w, SetAttribute(CAttribute::NoItalic)))?; } - if removed.contains(Modifier::UNDERLINED) { + if removed.intersects(Modifier::ANY_UNDERLINE) { map_error(queue!(w, SetAttribute(CAttribute::NoUnderline)))?; } if removed.contains(Modifier::DIM) { @@ -179,6 +179,18 @@ impl ModifierDiff { if added.contains(Modifier::UNDERLINED) { map_error(queue!(w, SetAttribute(CAttribute::Underlined)))?; } + if added.contains(Modifier::UNDERCURLED) { + map_error(queue!(w, SetAttribute(CAttribute::Undercurled)))?; + } + if added.contains(Modifier::UNDERDOTTED) { + map_error(queue!(w, SetAttribute(CAttribute::Underdotted)))?; + } + if added.contains(Modifier::UNDERDASHED) { + map_error(queue!(w, SetAttribute(CAttribute::Underdashed)))?; + } + if added.contains(Modifier::DOUBLE_UNDERLINED) { + map_error(queue!(w, SetAttribute(CAttribute::DoubleUnderlined)))?; + } if added.contains(Modifier::DIM) { map_error(queue!(w, SetAttribute(CAttribute::Dim)))?; } diff --git a/helix-view/src/graphics.rs b/helix-view/src/graphics.rs index fb3c8b3f..15492119 100644 --- a/helix-view/src/graphics.rs +++ b/helix-view/src/graphics.rs @@ -327,17 +327,27 @@ bitflags! { /// /// let m = Modifier::BOLD | Modifier::ITALIC; /// ``` - #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(rename_all = "kebab-case"))] pub struct Modifier: u16 { - const BOLD = 0b0000_0000_0001; - const DIM = 0b0000_0000_0010; - const ITALIC = 0b0000_0000_0100; - const UNDERLINED = 0b0000_0000_1000; - const SLOW_BLINK = 0b0000_0001_0000; - const RAPID_BLINK = 0b0000_0010_0000; - const REVERSED = 0b0000_0100_0000; - const HIDDEN = 0b0000_1000_0000; - const CROSSED_OUT = 0b0001_0000_0000; + 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; } } @@ -355,6 +365,10 @@ impl FromStr for Modifier { "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"), } } diff --git a/runtime/themes/onedark.toml b/runtime/themes/onedark.toml index 1e7d9af1..a4cc12eb 100644 --- a/runtime/themes/onedark.toml +++ b/runtime/themes/onedark.toml @@ -39,7 +39,7 @@ "diff.delta" = "gold" "diff.minus" = "red" -diagnostic = { modifiers = ["underlined"] } +diagnostic = { modifiers = ["undercurled"] } "info" = { fg = "blue", modifiers = ["bold"] } "hint" = { fg = "green", modifiers = ["bold"] } "warning" = { fg = "yellow", modifiers = ["bold"] } From 3ad7d543ca17963f0839b1a6cd8abacdb5c60cf7 Mon Sep 17 00:00:00 2001 From: A-Walrus Date: Thu, 11 Aug 2022 14:10:29 +0300 Subject: [PATCH 002/183] Add separate color for underlines --- helix-tui/src/backend/crossterm.rs | 8 +++++++- helix-tui/src/buffer.rs | 7 +++++++ helix-view/src/graphics.rs | 19 +++++++++++++++++++ helix-view/src/theme.rs | 1 + runtime/themes/dark_plus.toml | 3 ++- 5 files changed, 36 insertions(+), 2 deletions(-) diff --git a/helix-tui/src/backend/crossterm.rs b/helix-tui/src/backend/crossterm.rs index 252036f3..fe9da919 100644 --- a/helix-tui/src/backend/crossterm.rs +++ b/helix-tui/src/backend/crossterm.rs @@ -4,7 +4,7 @@ use crossterm::{ execute, queue, style::{ Attribute as CAttribute, Color as CColor, Print, SetAttribute, SetBackgroundColor, - SetForegroundColor, + SetForegroundColor, SetUnderlineColor, }, terminal::{self, Clear, ClearType}, }; @@ -47,6 +47,7 @@ where { let mut fg = Color::Reset; let mut bg = Color::Reset; + let mut underline = Color::Reset; let mut modifier = Modifier::empty(); let mut last_pos: Option<(u16, u16)> = None; for (x, y, cell) in content { @@ -73,6 +74,11 @@ where map_error(queue!(self.buffer, SetBackgroundColor(color)))?; bg = cell.bg; } + if cell.underline != underline { + let color = CColor::from(cell.underline); + map_error(queue!(self.buffer, SetUnderlineColor(color)))?; + underline = cell.underline; + } map_error(queue!(self.buffer, Print(&cell.symbol)))?; } diff --git a/helix-tui/src/buffer.rs b/helix-tui/src/buffer.rs index 21c53aad..14f3ecaf 100644 --- a/helix-tui/src/buffer.rs +++ b/helix-tui/src/buffer.rs @@ -11,6 +11,7 @@ pub struct Cell { pub symbol: String, pub fg: Color, pub bg: Color, + pub underline: Color, pub modifier: Modifier, } @@ -44,6 +45,9 @@ impl Cell { if let Some(c) = style.bg { self.bg = c; } + if let Some(c) = style.underline { + self.underline = c; + } self.modifier.insert(style.add_modifier); self.modifier.remove(style.sub_modifier); self @@ -53,6 +57,7 @@ impl Cell { Style::default() .fg(self.fg) .bg(self.bg) + .underline(self.bg) .add_modifier(self.modifier) } @@ -61,6 +66,7 @@ impl Cell { self.symbol.push(' '); self.fg = Color::Reset; self.bg = Color::Reset; + self.underline = Color::Reset; self.modifier = Modifier::empty(); } } @@ -71,6 +77,7 @@ impl Default for Cell { symbol: " ".into(), fg: Color::Reset, bg: Color::Reset, + underline: Color::Reset, modifier: Modifier::empty(), } } diff --git a/helix-view/src/graphics.rs b/helix-view/src/graphics.rs index 15492119..c995f60c 100644 --- a/helix-view/src/graphics.rs +++ b/helix-view/src/graphics.rs @@ -440,6 +440,7 @@ impl FromStr for Modifier { pub struct Style { pub fg: Option, pub bg: Option, + pub underline: Option, pub add_modifier: Modifier, pub sub_modifier: Modifier, } @@ -449,6 +450,7 @@ impl Default for Style { Style { fg: None, bg: None, + underline: None, add_modifier: Modifier::empty(), sub_modifier: Modifier::empty(), } @@ -461,6 +463,7 @@ impl Style { Style { fg: Some(Color::Reset), bg: Some(Color::Reset), + underline: Some(Color::Reset), add_modifier: Modifier::empty(), sub_modifier: Modifier::all(), } @@ -496,6 +499,21 @@ impl Style { self } + /// Changes the underline color. + /// + /// ## Examples + /// + /// ```rust + /// # use helix_view::graphics::{Color, Style}; + /// let style = Style::default().underline(Color::Blue); + /// let diff = Style::default().underline(Color::Red); + /// assert_eq!(style.patch(diff), Style::default().underline(Color::Red)); + /// ``` + pub fn underline(mut self, color: Color) -> Style { + self.underline = Some(color); + self + } + /// Changes the text emphasis. /// /// When applied, it adds the given modifier to the `Style` modifiers. @@ -552,6 +570,7 @@ 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.add_modifier.remove(other.sub_modifier); self.add_modifier.insert(other.add_modifier); diff --git a/helix-view/src/theme.rs b/helix-view/src/theme.rs index fa5fa702..5ce1b2c5 100644 --- a/helix-view/src/theme.rs +++ b/helix-view/src/theme.rs @@ -269,6 +269,7 @@ impl ThemePalette { 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)?), "modifiers" => { let modifiers = value .as_array() diff --git a/runtime/themes/dark_plus.toml b/runtime/themes/dark_plus.toml index f99da4fb..d1a5756e 100644 --- a/runtime/themes/dark_plus.toml +++ b/runtime/themes/dark_plus.toml @@ -92,7 +92,8 @@ "info" = { fg = "light_blue" } "hint" = { fg = "light_gray3" } -diagnostic = { modifiers = ["underlined"] } +"diagnostic.error" = {underline = "red", modifiers = ["undercurled"] } +"diagnostic" = {underline = "gold", modifiers = ["undercurled"] } [palette] white = "#ffffff" From 79a39c1063eca995bf75694743dc5eb3c905fa9c Mon Sep 17 00:00:00 2001 From: A-Walrus Date: Sun, 4 Sep 2022 11:28:55 +0300 Subject: [PATCH 003/183] Fix failing tests Add underline field to doctests, and fix bugs --- helix-tui/src/buffer.rs | 5 +++-- helix-tui/src/text.rs | 4 ++++ helix-view/src/graphics.rs | 2 ++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/helix-tui/src/buffer.rs b/helix-tui/src/buffer.rs index 14f3ecaf..3036608d 100644 --- a/helix-tui/src/buffer.rs +++ b/helix-tui/src/buffer.rs @@ -57,7 +57,7 @@ impl Cell { Style::default() .fg(self.fg) .bg(self.bg) - .underline(self.bg) + .underline(self.underline) .add_modifier(self.modifier) } @@ -104,7 +104,8 @@ impl Default for Cell { /// symbol: String::from("r"), /// fg: Color::Red, /// bg: Color::White, -/// modifier: Modifier::empty() +/// underline: Color::Reset, +/// modifier: Modifier::empty(), /// }); /// buf[(5, 0)].set_char('x'); /// assert_eq!(buf[(5, 0)].symbol, "x"); diff --git a/helix-tui/src/text.rs b/helix-tui/src/text.rs index 602090e5..73d58803 100644 --- a/helix-tui/src/text.rs +++ b/helix-tui/src/text.rs @@ -134,6 +134,7 @@ impl<'a> Span<'a> { /// style: Style { /// fg: Some(Color::Yellow), /// bg: Some(Color::Black), + /// underline: None, /// add_modifier: Modifier::empty(), /// sub_modifier: Modifier::empty(), /// }, @@ -143,6 +144,7 @@ impl<'a> Span<'a> { /// style: Style { /// fg: Some(Color::Yellow), /// bg: Some(Color::Black), + /// underline: None, /// add_modifier: Modifier::empty(), /// sub_modifier: Modifier::empty(), /// }, @@ -152,6 +154,7 @@ impl<'a> Span<'a> { /// style: Style { /// fg: Some(Color::Yellow), /// bg: Some(Color::Black), + /// underline: None, /// add_modifier: Modifier::empty(), /// sub_modifier: Modifier::empty(), /// }, @@ -161,6 +164,7 @@ impl<'a> Span<'a> { /// style: Style { /// fg: Some(Color::Yellow), /// bg: Some(Color::Black), + /// underline: None, /// add_modifier: Modifier::empty(), /// sub_modifier: Modifier::empty(), /// }, diff --git a/helix-view/src/graphics.rs b/helix-view/src/graphics.rs index c995f60c..6c854fd0 100644 --- a/helix-view/src/graphics.rs +++ b/helix-view/src/graphics.rs @@ -405,6 +405,7 @@ impl FromStr for Modifier { /// fg: Some(Color::Yellow), /// bg: Some(Color::Red), /// add_modifier: Modifier::BOLD, +/// underline: Some(Color::Reset), /// sub_modifier: Modifier::empty(), /// }, /// buffer[(0, 0)].style(), @@ -429,6 +430,7 @@ impl FromStr for Modifier { /// Style { /// fg: Some(Color::Yellow), /// bg: Some(Color::Reset), +/// underline: Some(Color::Reset), /// add_modifier: Modifier::empty(), /// sub_modifier: Modifier::empty(), /// }, From de72b9c04caf1fe12fd6076646657c456403c229 Mon Sep 17 00:00:00 2001 From: A-Walrus Date: Sun, 4 Sep 2022 17:57:14 +0300 Subject: [PATCH 004/183] Update theme documentation --- book/src/themes.md | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/book/src/themes.md b/book/src/themes.md index 9908456f..32ff2498 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", modifiers = ["bold", "italic"] } +key = { fg = "#ffffff", bg = "#000000", underline = "#ff0000", modifiers = ["bold", "italic", "undercurled"] } ``` -where `key` 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. +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. To specify only the foreground color: @@ -77,17 +77,21 @@ The following values may be used as modifiers. Less common modifiers might not be supported by your terminal emulator. -| Modifier | -| --- | -| `bold` | -| `dim` | -| `italic` | -| `underlined` | -| `slow_blink` | -| `rapid_blink` | -| `reversed` | -| `hidden` | -| `crossed_out` | +| Modifier | +| --- | +| `bold` | +| `dim` | +| `italic` | +| `underlined` | +| `undercurled` | +| `underdashed` | +| `underdotted` | +| `double-underlined` | +| `slow_blink` | +| `rapid_blink` | +| `reversed` | +| `hidden` | +| `crossed_out` | ### Scopes From 79d3d44c3db48365597eefd274e868bc1e15de57 Mon Sep 17 00:00:00 2001 From: Gokul Soumya Date: Wed, 7 Sep 2022 17:28:58 +0530 Subject: [PATCH 005/183] Detect extended underline support using terminfo The cxterminfo crate has been used over popular alternatives like `term` since it supports querying for extended capabilities and also for it's small codebase size (which will make it easy to inline it into helix in the future if required). --- Cargo.lock | 7 ++++ helix-tui/Cargo.toml | 1 + helix-tui/src/backend/crossterm.rs | 55 ++++++++++++++++++++++++++---- runtime/themes/onedark.toml | 5 ++- 4 files changed, 60 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e5edcaac..f980c417 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -176,6 +176,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "cxterminfo" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da92c5e3aaf2cc1fea346d9b3bac0c59c6ffc1d1d46f18d991d449912a3e6f07" + [[package]] name = "dirs-next" version = "2.0.0" @@ -504,6 +510,7 @@ dependencies = [ "bitflags", "cassowary", "crossterm", + "cxterminfo", "helix-core", "helix-view", "serde", diff --git a/helix-tui/Cargo.toml b/helix-tui/Cargo.toml index b220c64f..1c6a6a8d 100644 --- a/helix-tui/Cargo.toml +++ b/helix-tui/Cargo.toml @@ -20,6 +20,7 @@ bitflags = "1.3" cassowary = "0.3" unicode-segmentation = "1.10" crossterm = { version = "0.25", optional = true } +cxterminfo = "0.2" serde = { version = "1", "optional" = true, features = ["derive"]} helix-view = { version = "0.6", path = "../helix-view", features = ["term"] } helix-core = { version = "0.6", path = "../helix-core" } diff --git a/helix-tui/src/backend/crossterm.rs b/helix-tui/src/backend/crossterm.rs index fe9da919..3a50074e 100644 --- a/helix-tui/src/backend/crossterm.rs +++ b/helix-tui/src/backend/crossterm.rs @@ -11,8 +11,38 @@ use crossterm::{ use helix_view::graphics::{Color, CursorKind, Modifier, Rect}; use std::io::{self, Write}; +fn vte_version() -> Option { + std::env::var("VTE_VERSION").ok()?.parse().ok() +} + +/// Describes terminal capabilities like extended underline, truecolor, etc. +#[derive(Copy, Clone, Debug, Default)] +struct Capabilities { + /// Support for undercurled, underdashed, etc. + has_extended_underlines: bool, +} + +impl Capabilities { + /// Detect capabilities from the terminfo database located based + /// on the $TERM environment variable. If detection fails, returns + /// a default value where no capability is supported. + pub fn from_env_or_default() -> Self { + match cxterminfo::terminfo::TermInfo::from_env() { + Err(_) => Capabilities::default(), + Ok(t) => Capabilities { + // Smulx, VTE: https://unix.stackexchange.com/a/696253/246284 + // Su (used by kitty): https://sw.kovidgoyal.net/kitty/underlines + has_extended_underlines: t.get_ext_string("Smulx").is_some() + || *t.get_ext_bool("Su").unwrap_or(&false) + || vte_version() >= Some(5102), + }, + } + } +} + pub struct CrosstermBackend { buffer: W, + capabilities: Capabilities, } impl CrosstermBackend @@ -20,7 +50,10 @@ where W: Write, { pub fn new(buffer: W) -> CrosstermBackend { - CrosstermBackend { buffer } + CrosstermBackend { + buffer, + capabilities: Capabilities::from_env_or_default(), + } } } @@ -61,7 +94,7 @@ where from: modifier, to: cell.modifier, }; - diff.queue(&mut self.buffer)?; + diff.queue(&mut self.buffer, self.capabilities)?; modifier = cell.modifier; } if cell.fg != fg { @@ -141,7 +174,7 @@ struct ModifierDiff { } impl ModifierDiff { - fn queue(&self, mut w: W) -> io::Result<()> + fn queue(&self, mut w: W, caps: Capabilities) -> io::Result<()> where W: io::Write, { @@ -172,6 +205,14 @@ 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)))?; @@ -186,16 +227,16 @@ impl ModifierDiff { map_error(queue!(w, SetAttribute(CAttribute::Underlined)))?; } if added.contains(Modifier::UNDERCURLED) { - map_error(queue!(w, SetAttribute(CAttribute::Undercurled)))?; + queue_styled_underline(CAttribute::Undercurled, &mut w)?; } if added.contains(Modifier::UNDERDOTTED) { - map_error(queue!(w, SetAttribute(CAttribute::Underdotted)))?; + queue_styled_underline(CAttribute::Underdotted, &mut w)?; } if added.contains(Modifier::UNDERDASHED) { - map_error(queue!(w, SetAttribute(CAttribute::Underdashed)))?; + queue_styled_underline(CAttribute::Underdashed, &mut w)?; } if added.contains(Modifier::DOUBLE_UNDERLINED) { - map_error(queue!(w, SetAttribute(CAttribute::DoubleUnderlined)))?; + queue_styled_underline(CAttribute::DoubleUnderlined, &mut w)?; } if added.contains(Modifier::DIM) { map_error(queue!(w, SetAttribute(CAttribute::Dim)))?; diff --git a/runtime/themes/onedark.toml b/runtime/themes/onedark.toml index a4cc12eb..e2bc2c47 100644 --- a/runtime/themes/onedark.toml +++ b/runtime/themes/onedark.toml @@ -39,7 +39,10 @@ "diff.delta" = "gold" "diff.minus" = "red" -diagnostic = { modifiers = ["undercurled"] } +"diagnostic.info" = { underline = "blue", modifiers = ["undercurled"] } +"diagnostic.hint" = { underline = "green", modifiers = ["undercurled"] } +"diagnostic.warning" = { underline = "yellow", modifiers = ["undercurled"] } +"diagnostic.error" = { underline = "red", modifiers = ["undercurled"] } "info" = { fg = "blue", modifiers = ["bold"] } "hint" = { fg = "green", modifiers = ["bold"] } "warning" = { fg = "yellow", modifiers = ["bold"] } From 71ee589bbc723e7a55585ddc2ca43c29ee93fabe Mon Sep 17 00:00:00 2001 From: Pascal Kuthe Date: Sat, 1 Oct 2022 02:15:25 +0200 Subject: [PATCH 006/183] 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 | 27 +++++--- helix-tui/src/text.rs | 12 ++-- helix-view/src/graphics.rs | 101 ++++++++++++++++++++--------- helix-view/src/gutter.rs | 4 +- helix-view/src/theme.rs | 23 ++++++- runtime/themes/dark_plus.toml | 4 +- runtime/themes/onedark.toml | 8 +-- 9 files changed, 167 insertions(+), 91 deletions(-) diff --git a/book/src/themes.md b/book/src/themes.md index 32ff2498..450b3364 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 3a50074e..3e6dc5f5 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 3036608d..424e6d32 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(), } } @@ -94,7 +102,7 @@ impl Default for Cell { /// /// ``` /// use helix_tui::buffer::{Buffer, Cell}; -/// use helix_view::graphics::{Rect, Color, Style, Modifier}; +/// use helix_view::graphics::{Rect, Color, UnderlineStyle, Style, Modifier}; /// /// let mut buf = Buffer::empty(Rect{x: 0, y: 0, width: 10, height: 5}); /// buf[(0, 2)].set_symbol("x"); @@ -104,7 +112,8 @@ impl Default for Cell { /// symbol: String::from("r"), /// fg: Color::Red, /// bg: Color::White, -/// underline: Color::Reset, +/// underline_color: Color::Reset, +/// underline_style: UnderlineStyle::Reset, /// modifier: Modifier::empty(), /// }); /// buf[(5, 0)].set_char('x'); diff --git a/helix-tui/src/text.rs b/helix-tui/src/text.rs index 73d58803..1bfe5ee1 100644 --- a/helix-tui/src/text.rs +++ b/helix-tui/src/text.rs @@ -134,7 +134,8 @@ impl<'a> Span<'a> { /// style: Style { /// fg: Some(Color::Yellow), /// bg: Some(Color::Black), - /// underline: None, + /// underline_color: None, + /// underline_style: None, /// add_modifier: Modifier::empty(), /// sub_modifier: Modifier::empty(), /// }, @@ -144,7 +145,8 @@ impl<'a> Span<'a> { /// style: Style { /// fg: Some(Color::Yellow), /// bg: Some(Color::Black), - /// underline: None, + /// underline_color: None, + /// underline_style: None, /// add_modifier: Modifier::empty(), /// sub_modifier: Modifier::empty(), /// }, @@ -154,7 +156,8 @@ impl<'a> Span<'a> { /// style: Style { /// fg: Some(Color::Yellow), /// bg: Some(Color::Black), - /// underline: None, + /// underline_color: None, + /// underline_style: None, /// add_modifier: Modifier::empty(), /// sub_modifier: Modifier::empty(), /// }, @@ -164,7 +167,8 @@ impl<'a> Span<'a> { /// style: Style { /// fg: Some(Color::Yellow), /// bg: Some(Color::Black), - /// underline: None, + /// underline_color: None, + /// underline_style: None, /// add_modifier: Modifier::empty(), /// sub_modifier: Modifier::empty(), /// }, diff --git a/helix-view/src/graphics.rs b/helix-view/src/graphics.rs index 6c854fd0..01344748 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"), } } @@ -389,7 +411,7 @@ impl FromStr for Modifier { /// just S3. /// /// ```rust -/// # use helix_view::graphics::{Rect, Color, Modifier, Style}; +/// # use helix_view::graphics::{Rect, Color, UnderlineStyle, Modifier, Style}; /// # use helix_tui::buffer::Buffer; /// let styles = [ /// Style::default().fg(Color::Blue).add_modifier(Modifier::BOLD | Modifier::ITALIC), @@ -405,7 +427,8 @@ impl FromStr for Modifier { /// fg: Some(Color::Yellow), /// bg: Some(Color::Red), /// add_modifier: Modifier::BOLD, -/// underline: Some(Color::Reset), +/// underline_color: Some(Color::Reset), +/// underline_style: Some(UnderlineStyle::Reset), /// sub_modifier: Modifier::empty(), /// }, /// buffer[(0, 0)].style(), @@ -416,7 +439,7 @@ impl FromStr for Modifier { /// reset all properties until that point use [`Style::reset`]. /// /// ``` -/// # use helix_view::graphics::{Rect, Color, Modifier, Style}; +/// # use helix_view::graphics::{Rect, Color, UnderlineStyle, Modifier, Style}; /// # use helix_tui::buffer::Buffer; /// let styles = [ /// Style::default().fg(Color::Blue).add_modifier(Modifier::BOLD | Modifier::ITALIC), @@ -430,7 +453,8 @@ impl FromStr for Modifier { /// Style { /// fg: Some(Color::Yellow), /// bg: Some(Color::Reset), -/// underline: Some(Color::Reset), +/// underline_color: Some(Color::Reset), +/// underline_style: Some(UnderlineStyle::Reset), /// add_modifier: Modifier::empty(), /// sub_modifier: Modifier::empty(), /// }, @@ -442,7 +466,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 +477,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 +491,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 +534,27 @@ impl Style { /// /// ```rust /// # use helix_view::graphics::{Color, Style}; - /// let style = Style::default().underline(Color::Blue); - /// let diff = Style::default().underline(Color::Red); - /// assert_eq!(style.patch(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(Color::Red)); + /// ``` + 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(mut self, color: Color) -> Style { - self.underline = Some(color); + pub fn underline_style(mut self, style: UnderlineStyle) -> Style { + self.underline_style = Some(style); self } @@ -572,7 +614,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 ab0e2986..2c207d27 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 5ce1b2c5..90185937 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 d1a5756e..fa6b34ab 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 e2bc2c47..5f337a8d 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"] } From 114610f7dc5d6395ef5cce9111a363f7c8d879a4 Mon Sep 17 00:00:00 2001 From: Pascal Kuthe Date: Thu, 6 Oct 2022 20:46:24 +0200 Subject: [PATCH 007/183] switch to termini for terminfo --- Cargo.lock | 17 ++++++++++------- helix-tui/Cargo.toml | 2 +- helix-tui/src/backend/crossterm.rs | 6 +++--- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f980c417..03b3a6d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -176,12 +176,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "cxterminfo" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da92c5e3aaf2cc1fea346d9b3bac0c59c6ffc1d1d46f18d991d449912a3e6f07" - [[package]] name = "dirs-next" version = "2.0.0" @@ -510,10 +504,10 @@ dependencies = [ "bitflags", "cassowary", "crossterm", - "cxterminfo", "helix-core", "helix-view", "serde", + "termini", "unicode-segmentation", ] @@ -1105,6 +1099,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "termini" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00733d628ac0a8bd4fd3171a28eb6c09759ae1b43d8b587eadebaccee01d01a3" +dependencies = [ + "dirs-next", +] + [[package]] name = "textwrap" version = "0.15.1" diff --git a/helix-tui/Cargo.toml b/helix-tui/Cargo.toml index 1c6a6a8d..a4a1c389 100644 --- a/helix-tui/Cargo.toml +++ b/helix-tui/Cargo.toml @@ -20,7 +20,7 @@ bitflags = "1.3" cassowary = "0.3" unicode-segmentation = "1.10" crossterm = { version = "0.25", optional = true } -cxterminfo = "0.2" +termini = "0.1" serde = { version = "1", "optional" = true, features = ["derive"]} helix-view = { version = "0.6", path = "../helix-view", features = ["term"] } helix-core = { version = "0.6", path = "../helix-core" } diff --git a/helix-tui/src/backend/crossterm.rs b/helix-tui/src/backend/crossterm.rs index 3e6dc5f5..4d8c6650 100644 --- a/helix-tui/src/backend/crossterm.rs +++ b/helix-tui/src/backend/crossterm.rs @@ -27,13 +27,13 @@ impl Capabilities { /// on the $TERM environment variable. If detection fails, returns /// a default value where no capability is supported. pub fn from_env_or_default() -> Self { - match cxterminfo::terminfo::TermInfo::from_env() { + match termini::TermInfo::from_env() { Err(_) => Capabilities::default(), Ok(t) => Capabilities { // Smulx, VTE: https://unix.stackexchange.com/a/696253/246284 // Su (used by kitty): https://sw.kovidgoyal.net/kitty/underlines - has_extended_underlines: t.get_ext_string("Smulx").is_some() - || *t.get_ext_bool("Su").unwrap_or(&false) + has_extended_underlines: t.extended_cap("Smulx").is_some() + || t.extended_cap("Su").is_some() || vte_version() >= Some(5102), }, } From 7bc324fde986fab2ded2ad29d7b5244521eddc44 Mon Sep 17 00:00:00 2001 From: Pascal Kuthe Date: Thu, 6 Oct 2022 20:50:54 +0200 Subject: [PATCH 008/183] make casing consistent with other configuration --- book/src/themes.md | 6 +++--- helix-view/src/theme.rs | 4 ++-- runtime/themes/dark_plus.toml | 4 ++-- runtime/themes/onedark.toml | 8 ++++---- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/book/src/themes.md b/book/src/themes.md index 450b3364..b386973a 100644 --- a/book/src/themes.md +++ b/book/src/themes.md @@ -13,7 +13,7 @@ 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_color = "#ff0000", underline_style = "curl", modifiers = ["bold", "italic"] } +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_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. @@ -91,7 +91,7 @@ Less common modifiers might not be supported by your terminal emulator. ### Underline Style -One of the following values may be used as an `underline_styles`. +One of the following values may be used as an `underline-style`. Some styles might not be supported by your terminal emulator. @@ -101,7 +101,7 @@ Some styles might not be supported by your terminal emulator. | `curl` | | `dashed` | | `dot` | -| `double-line` | +| `double_line` | ### Scopes diff --git a/helix-view/src/theme.rs b/helix-view/src/theme.rs index 90185937..b1c96f94 100644 --- a/helix-view/src/theme.rs +++ b/helix-view/src/theme.rs @@ -277,8 +277,8 @@ impl ThemePalette { match name.as_str() { "fg" => *style = style.fg(self.parse_color(value)?), "bg" => *style = style.bg(self.parse_color(value)?), - "underline_color" => *style = style.underline_color(self.parse_color(value)?), - "underline_style" => { + "underline-color" => *style = style.underline_color(self.parse_color(value)?), + "underline-style" => { warn!("found style"); *style = style.underline_style(Self::parse_underline_style(&value)?) } diff --git a/runtime/themes/dark_plus.toml b/runtime/themes/dark_plus.toml index fa6b34ab..afbd1b71 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_color = "red", underline_style = "curl"} -"diagnostic" = {underline_color = "gold", underline_style = "curl" } +"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 5f337a8d..cce0474f 100644 --- a/runtime/themes/onedark.toml +++ b/runtime/themes/onedark.toml @@ -39,10 +39,10 @@ "diff.delta" = "gold" "diff.minus" = "red" -"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" } +"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"] } From 4c36c067b06e3bba24e0b2bd0f40f259ca6c8b41 Mon Sep 17 00:00:00 2001 From: Pascal Kuthe Date: Fri, 7 Oct 2022 00:09:55 +0200 Subject: [PATCH 009/183] avoid visual artificats on terminal emulators that do not support underline colors --- helix-tui/src/backend/crossterm.rs | 77 ++++++++++++++++++++++++++---- 1 file changed, 68 insertions(+), 9 deletions(-) diff --git a/helix-tui/src/backend/crossterm.rs b/helix-tui/src/backend/crossterm.rs index 4d8c6650..7b3b0817 100644 --- a/helix-tui/src/backend/crossterm.rs +++ b/helix-tui/src/backend/crossterm.rs @@ -4,13 +4,16 @@ use crossterm::{ execute, queue, style::{ Attribute as CAttribute, Color as CColor, Print, SetAttribute, SetBackgroundColor, - SetForegroundColor, SetUnderlineColor, + SetForegroundColor, }, terminal::{self, Clear, ClearType}, + Command, }; use helix_view::graphics::{Color, CursorKind, Modifier, Rect, UnderlineStyle}; -use std::io::{self, Write}; - +use std::{ + fmt, + io::{self, Write}, +}; fn vte_version() -> Option { std::env::var("VTE_VERSION").ok()?.parse().ok() } @@ -108,18 +111,19 @@ where map_error(queue!(self.buffer, SetBackgroundColor(color)))?; bg = cell.bg; } - if cell.underline_color != underline_color { - let color = CColor::from(cell.underline_color); - map_error(queue!(self.buffer, SetUnderlineColor(color)))?; - 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 => (), + UnderlineStyle::Reset | UnderlineStyle::Line => (), _ => new_underline_style = UnderlineStyle::Line, } + + if cell.underline_color != underline_color { + let color = CColor::from(cell.underline_color); + map_error(queue!(self.buffer, SetUnderlineColor(color)))?; + underline_color = cell.underline_color; + } } if new_underline_style != underline_style { @@ -244,3 +248,58 @@ impl ModifierDiff { Ok(()) } } + +/// Crossterm uses semicolon as a seperator for colors +/// this is actually not spec compliant (altough commonly supported) +/// However the correct approach is to use colons as a seperator. +/// This usually doesn't make a difference for emulators that do support colored underlines. +/// However terminals that do not support colored underlines will ignore underlines colors with colons +/// while escape sequences with semicolons are always processed which leads to weird visual artifacts. +/// See [this nvim issue](https://github.com/neovim/neovim/issues/9270) for details +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct SetUnderlineColor(pub CColor); + +impl Command for SetUnderlineColor { + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + let color = self.0; + + if color == CColor::Reset { + write!(f, "\x1b[59m")?; + return Ok(()); + } + f.write_str("\x1b[58:")?; + + let res = match color { + CColor::Black => f.write_str("5:0"), + CColor::DarkGrey => f.write_str("5:8"), + CColor::Red => f.write_str("5:9"), + CColor::DarkRed => f.write_str("5:1"), + CColor::Green => f.write_str("5:10"), + CColor::DarkGreen => f.write_str("5:2"), + CColor::Yellow => f.write_str("5:11"), + CColor::DarkYellow => f.write_str("5:3"), + CColor::Blue => f.write_str("5:12"), + CColor::DarkBlue => f.write_str("5:4"), + CColor::Magenta => f.write_str("5:13"), + CColor::DarkMagenta => f.write_str("5:5"), + CColor::Cyan => f.write_str("5:14"), + CColor::DarkCyan => f.write_str("5:6"), + CColor::White => f.write_str("5:15"), + CColor::Grey => f.write_str("5:7"), + CColor::Rgb { r, g, b } => write!(f, "2::{}:{}:{}", r, g, b), + CColor::AnsiValue(val) => write!(f, "5:{}", val), + _ => Ok(()), + }; + res?; + write!(f, "m")?; + Ok(()) + } + + #[cfg(windows)] + fn execute_winapi(&self) -> crossterm::Result<()> { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "SetUnderlineColor not supported by winapi.", + )) + } +} From ad0eb4094b39e458e9681eaa83539ea6cf09e5de Mon Sep 17 00:00:00 2001 From: pascalkuthe Date: Sat, 8 Oct 2022 16:39:02 +0200 Subject: [PATCH 010/183] add deprectation not for underlined modifier to docs --- book/src/themes.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/book/src/themes.md b/book/src/themes.md index b386973a..c5a623b0 100644 --- a/book/src/themes.md +++ b/book/src/themes.md @@ -89,6 +89,9 @@ Less common modifiers might not be supported by your terminal emulator. | `hidden` | | `crossed_out` | +> Note: The `underlined` modifier is deprecated and only available for backwards compatability. +> It's behaviour is equivalent to `underline-style="line"` + ### Underline Style One of the following values may be used as an `underline-style`. From 2f7088c1f37f11606944a9db52814a652f97fdcd Mon Sep 17 00:00:00 2001 From: Pascal Kuthe Date: Sat, 8 Oct 2022 17:43:32 +0200 Subject: [PATCH 011/183] fix typo Co-authored-by: Omnikar --- book/src/themes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/src/themes.md b/book/src/themes.md index c5a623b0..e84f8705 100644 --- a/book/src/themes.md +++ b/book/src/themes.md @@ -90,7 +90,7 @@ Less common modifiers might not be supported by your terminal emulator. | `crossed_out` | > Note: The `underlined` modifier is deprecated and only available for backwards compatability. -> It's behaviour is equivalent to `underline-style="line"` +> Its behaviour is equivalent to `underline-style="line"`. ### Underline Style From c15f1ea274d300feb23208324aa5b27d7274bebd Mon Sep 17 00:00:00 2001 From: A-Walrus <58790821+A-Walrus@users.noreply.github.com> Date: Sat, 8 Oct 2022 21:28:42 +0300 Subject: [PATCH 012/183] Add cursorcolumn (#4084) * Implement cursorcolumn * Add documentation * Separate column style from line with fallback * Fallback to cursorcolumn first * Switch to non-fallback try_get_exact Add new function `try_get_exact`, which doesn't perform fallback, and use that instead because the fallback behaviour is being handled manually. --- book/src/configuration.md | 1 + book/src/themes.md | 92 +++++++++++++++++++------------------ helix-term/src/ui/editor.rs | 52 ++++++++++++++++++++- helix-view/src/editor.rs | 3 ++ helix-view/src/theme.rs | 7 +++ 5 files changed, 109 insertions(+), 46 deletions(-) diff --git a/book/src/configuration.md b/book/src/configuration.md index 4761ebe4..707102f6 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -45,6 +45,7 @@ on unix operating systems. | `shell` | Shell to use when running external commands. | Unix: `["sh", "-c"]`
Windows: `["cmd", "/C"]` | | `line-number` | Line number display: `absolute` simply shows each line's number, while `relative` shows the distance from the current line. When unfocused or in insert mode, `relative` will still show absolute line numbers. | `absolute` | | `cursorline` | Highlight all lines with a cursor. | `false` | +| `cursorcolumn` | Highlight all columns with a cursor. | `false` | | `gutters` | Gutters to display: Available are `diagnostics` and `line-numbers` and `spacer`, note that `diagnostics` also includes other features like breakpoints, 1-width padding will be inserted if gutters is non-empty | `["diagnostics", "line-numbers"]` | | `auto-completion` | Enable automatic pop up of auto-completion. | `true` | | `auto-format` | Enable automatic formatting on save. | `true` | diff --git a/book/src/themes.md b/book/src/themes.md index 4cded68f..2563bb85 100644 --- a/book/src/themes.md +++ b/book/src/themes.md @@ -225,51 +225,53 @@ These scopes are used for theming the editor interface. - `hover` - for hover popup ui -| Key | Notes | -| --- | --- | -| `ui.background` | | -| `ui.background.separator` | Picker separator below input line | -| `ui.cursor` | | -| `ui.cursor.insert` | | -| `ui.cursor.select` | | -| `ui.cursor.match` | Matching bracket etc. | -| `ui.cursor.primary` | Cursor with primary selection | -| `ui.gutter` | Gutter | -| `ui.gutter.selected` | Gutter for the line the cursor is on | -| `ui.linenr` | Line numbers | -| `ui.linenr.selected` | Line number for the line the cursor is on | -| `ui.statusline` | Statusline | -| `ui.statusline.inactive` | Statusline (unfocused document) | -| `ui.statusline.normal` | Statusline mode during normal mode ([only if `editor.color-modes` is enabled][editor-section]) | -| `ui.statusline.insert` | Statusline mode during insert mode ([only if `editor.color-modes` is enabled][editor-section]) | -| `ui.statusline.select` | Statusline mode during select mode ([only if `editor.color-modes` is enabled][editor-section]) | -| `ui.statusline.separator` | Separator character in statusline | -| `ui.popup` | Documentation popups (e.g space-k) | -| `ui.popup.info` | Prompt for multiple key options | -| `ui.window` | Border lines separating splits | -| `ui.help` | Description box for commands | -| `ui.text` | Command prompts, popup text, etc. | -| `ui.text.focus` | | -| `ui.text.info` | The key: command text in `ui.popup.info` boxes | -| `ui.virtual.ruler` | Ruler columns (see the [`editor.rulers` config][editor-section])| -| `ui.virtual.whitespace` | Visible white-space characters | -| `ui.virtual.indent-guide` | Vertical indent width guides | -| `ui.menu` | Code and command completion menus | -| `ui.menu.selected` | Selected autocomplete item | -| `ui.menu.scroll` | `fg` sets thumb color, `bg` sets track color of scrollbar | -| `ui.selection` | For selections in the editing area | -| `ui.selection.primary` | | -| `ui.cursorline.primary` | The line of the primary cursor | -| `ui.cursorline.secondary` | The lines of any other cursors | -| `warning` | Diagnostics warning (gutter) | -| `error` | Diagnostics error (gutter) | -| `info` | Diagnostics info (gutter) | -| `hint` | Diagnostics hint (gutter) | -| `diagnostic` | Diagnostics fallback style (editing area) | -| `diagnostic.hint` | Diagnostics hint (editing area) | -| `diagnostic.info` | Diagnostics info (editing area) | -| `diagnostic.warning` | Diagnostics warning (editing area) | -| `diagnostic.error` | Diagnostics error (editing area) | +| Key | Notes | +| --- | --- | +| `ui.background` | | +| `ui.background.separator` | Picker separator below input line | +| `ui.cursor` | | +| `ui.cursor.insert` | | +| `ui.cursor.select` | | +| `ui.cursor.match` | Matching bracket etc. | +| `ui.cursor.primary` | Cursor with primary selection | +| `ui.gutter` | Gutter | +| `ui.gutter.selected` | Gutter for the line the cursor is on | +| `ui.linenr` | Line numbers | +| `ui.linenr.selected` | Line number for the line the cursor is on | +| `ui.statusline` | Statusline | +| `ui.statusline.inactive` | Statusline (unfocused document) | +| `ui.statusline.normal` | Statusline mode during normal mode ([only if `editor.color-modes` is enabled][editor-section]) | +| `ui.statusline.insert` | Statusline mode during insert mode ([only if `editor.color-modes` is enabled][editor-section]) | +| `ui.statusline.select` | Statusline mode during select mode ([only if `editor.color-modes` is enabled][editor-section]) | +| `ui.statusline.separator` | Separator character in statusline | +| `ui.popup` | Documentation popups (e.g space-k) | +| `ui.popup.info` | Prompt for multiple key options | +| `ui.window` | Border lines separating splits | +| `ui.help` | Description box for commands | +| `ui.text` | Command prompts, popup text, etc. | +| `ui.text.focus` | | +| `ui.text.info` | The key: command text in `ui.popup.info` boxes | +| `ui.virtual.ruler` | Ruler columns (see the [`editor.rulers` config][editor-section]) | +| `ui.virtual.whitespace` | Visible white-space characters | +| `ui.virtual.indent-guide` | Vertical indent width guides | +| `ui.menu` | Code and command completion menus | +| `ui.menu.selected` | Selected autocomplete item | +| `ui.menu.scroll` | `fg` sets thumb color, `bg` sets track color of scrollbar | +| `ui.selection` | For selections in the editing area | +| `ui.selection.primary` | | +| `ui.cursorline.primary` | The line of the primary cursor ([if cursorline is enabled][editor-section]) | +| `ui.cursorline.secondary` | The lines of any other cursors ([if cursorline is enabled][editor-section]) | +| `ui.cursorcolumn.primary` | The column of the primary cursor ([if cursorcolumn is enabled][editor-section]) | +| `ui.cursorcolumn.secondary` | The columns of any other cursors ([if cursorcolumn is enabled][editor-section]) | +| `warning` | Diagnostics warning (gutter) | +| `error` | Diagnostics error (gutter) | +| `info` | Diagnostics info (gutter) | +| `hint` | Diagnostics hint (gutter) | +| `diagnostic` | Diagnostics fallback style (editing area) | +| `diagnostic.hint` | Diagnostics hint (editing area) | +| `diagnostic.info` | Diagnostics info (editing area) | +| `diagnostic.warning` | Diagnostics warning (editing area) | +| `diagnostic.error` | Diagnostics error (editing area) | You can check compliance to spec with diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 97639ff2..47fb7a4a 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -13,7 +13,7 @@ use helix_core::{ movement::Direction, syntax::{self, HighlightEvent}, unicode::width::UnicodeWidthStr, - LineEnding, Position, Range, Selection, Transaction, + visual_coords_at_pos, LineEnding, Position, Range, Selection, Transaction, }; use helix_view::{ document::{Mode, SCRATCH_BUFFER_NAME}, @@ -118,6 +118,9 @@ impl EditorView { if is_focused && editor.config().cursorline { Self::highlight_cursorline(doc, view, surface, theme); } + if is_focused && editor.config().cursorcolumn { + Self::highlight_cursorcolumn(doc, view, surface, theme); + } let highlights = Self::doc_syntax_highlights(doc, view.offset, inner.height, theme); let highlights = syntax::merge(highlights, Self::doc_diagnostics_highlights(doc, theme)); @@ -830,6 +833,53 @@ impl EditorView { } } + /// Apply the highlighting on the columns where a cursor is active + pub fn highlight_cursorcolumn( + doc: &Document, + view: &View, + surface: &mut Surface, + theme: &Theme, + ) { + let text = doc.text().slice(..); + + // Manual fallback behaviour: + // ui.cursorcolumn.{p/s} -> ui.cursorcolumn -> ui.cursorline.{p/s} + let primary_style = theme + .try_get_exact("ui.cursorcolumn.primary") + .or_else(|| theme.try_get_exact("ui.cursorcolumn")) + .unwrap_or_else(|| theme.get("ui.cursorline.primary")); + let secondary_style = theme + .try_get_exact("ui.cursorcolumn.secondary") + .or_else(|| theme.try_get_exact("ui.cursorcolumn")) + .unwrap_or_else(|| theme.get("ui.cursorline.secondary")); + + let inner_area = view.inner_area(); + let offset = view.offset.col; + + let selection = doc.selection(view.id); + let primary = selection.primary(); + for range in selection.iter() { + let is_primary = primary == *range; + + let Position { row: _, col } = + visual_coords_at_pos(text, range.cursor(text), doc.tab_width()); + // if the cursor is horizontally in the view + if col >= offset && inner_area.width > (col - offset) as u16 { + let area = Rect::new( + inner_area.x + (col - offset) as u16, + view.area.y, + 1, + view.area.height, + ); + if is_primary { + surface.set_style(area, primary_style) + } else { + surface.set_style(area, secondary_style) + } + } + } + } + /// Handle events by looking them up in `self.keymaps`. Returns None /// if event was handled (a command was executed or a subkeymap was /// activated). Only KeymapResult::{NotFound, Cancelled} is returned diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index d09d0ac3..60b3880c 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -124,6 +124,8 @@ pub struct Config { pub line_number: LineNumber, /// Highlight the lines cursors are currently on. Defaults to false. pub cursorline: bool, + /// Highlight the columns cursors are currently on. Defaults to false. + pub cursorcolumn: bool, /// Gutters. Default ["diagnostics", "line-numbers"] pub gutters: Vec, /// Middle click paste support. Defaults to true. @@ -582,6 +584,7 @@ impl Default for Config { }, line_number: LineNumber::Absolute, cursorline: false, + cursorcolumn: false, gutters: vec![GutterType::Diagnostics, GutterType::LineNumbers], middle_click_paste: true, auto_pairs: AutoPairConfig::default(), diff --git a/helix-view/src/theme.rs b/helix-view/src/theme.rs index 85f5cc13..8a1f8b7e 100644 --- a/helix-view/src/theme.rs +++ b/helix-view/src/theme.rs @@ -277,6 +277,13 @@ impl Theme { .find_map(|s| self.styles.get(s).copied()) } + /// Get the style of a scope, without falling back to dot separated broader + /// scopes. For example if `ui.text.focus` is not defined in the theme, it + /// will return `None`, even if `ui.text` is. + pub fn try_get_exact(&self, scope: &str) -> Option