From 8d507c43fa866ab4c0eda9fd169f307fba2a1109 Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Wed, 18 Oct 2023 13:52:43 -0700 Subject: [PATCH] fix(backend): add feature flag for underline-color (#570) Windows 7 doesn't support the underline color attribute, so we need to make it optional. This commit adds a feature flag for the underline color attribute - it is enabled by default, but can be disabled by passing `--no-default-features` to cargo. We could specically check for Windows 7 and disable the feature flag automatically, but I think it's better for this check to be done by the crossterm crate, since it's the one that actually knows about the underlying terminal. To disable the feature flag in an application that supports Windows 7, add the following to your Cargo.toml: ```toml ratatui = { version = "0.24.0", default-features = false, features = ["crossterm"] } ``` Fixes https://github.com/ratatui-org/ratatui/issues/555 --- Cargo.toml | 26 +++++++++++++++++++------- Makefile.toml | 6 +++--- src/backend/crossterm.rs | 20 ++++++++++++++++---- src/buffer.rs | 26 +++++++++++++------------- src/style.rs | 23 +++++++++++++---------- src/widgets/gauge.rs | 4 ++-- 6 files changed, 66 insertions(+), 39 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 29891bee..ef0dc81b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,14 +23,8 @@ rust-version = "1.67.0" [badges] [dependencies] -#! The crate provides a set of optional features that can be enabled in your `cargo.toml` file. -#! -#! Generally an application will only use one backend, so you should only enable one of the following features: -## enables the [`CrosstermBackend`] backend and adds a dependency on the [Crossterm crate]. crossterm = { version = "0.27", optional = true } -## enables the [`TermionBackend`] backend and adds a dependency on the [Termion crate]. termion = { version = "2.0", optional = true } -## enables the [`TermwizBackend`] backend and adds a dependency on the [Termwiz crate]. termwiz = { version = "0.20.0", optional = true } serde = { version = "1", optional = true, features = ["derive"] } @@ -60,7 +54,20 @@ palette = "0.7.3" pretty_assertions = "1.4.0" [features] -default = ["crossterm"] +#! The crate provides a set of optional features that can be enabled in your `cargo.toml` file. +#! +## By default, we enable the crossterm backend as this is a reasonable choice for most applications +## as it is supported on Linux/Mac/Windows systems. We also enable the `underline-color` feature +## which allows you to set the underline color of text. +default = ["crossterm", "underline-color"] +#! Generally an application will only use one backend, so you should only enable one of the following features: +## enables the [`CrosstermBackend`] backend and adds a dependency on the [Crossterm crate]. +crossterm = ["dep:crossterm"] +## enables the [`TermionBackend`] backend and adds a dependency on the [Termion crate]. +termion = ["dep:termion"] +## enables the [`TermwizBackend`] backend and adds a dependency on the [Termwiz crate]. +termwiz = ["dep:termwiz"] + #! The following optional features are available for all backends: ## enables serialization and deserialization of style and color types using the [Serde crate]. ## This is useful if you want to save themes to a file. @@ -77,6 +84,11 @@ all-widgets = ["widget-calendar"] ## enables the [`calendar`] widget module and adds a dependency on the [Time crate]. widget-calendar = ["dep:time"] +#! Underline color is only supported by the [`CrosstermBackend`] backend, and is not supported +#! on Windows 7. +## enables the backend code that sets the underline color. +underline-color = ["dep:crossterm"] + [package.metadata.docs.rs] all-features = true # see https://doc.rust-lang.org/nightly/rustdoc/scraped-examples.html diff --git a/Makefile.toml b/Makefile.toml index 7fc6677b..26194e27 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -9,9 +9,9 @@ ALL_FEATURES = "all-widgets,macros,serde" # Windows does not support building termion, so this avoids the build failure by providing two # sets of flags, one for Windows and one for other platforms. -# Windows: --features=all-widgets,macros,serde,crossterm,termwiz -# Other: --all-features -ALL_FEATURES_FLAG = { source = "${CARGO_MAKE_RUST_TARGET_OS}", default_value = "--all-features", mapping = { "windows" = "--features=all-widgets,macros,serde,crossterm,termwiz" } } +# Windows: --features=all-widgets,macros,serde,crossterm,termwiz,underline-color +# Other: --features=all-widgets,macros,serde,crossterm,termion,termwiz,underline-color +ALL_FEATURES_FLAG = { source = "${CARGO_MAKE_RUST_TARGET_OS}", default_value = "--features=all-widgets,macros,serde,crossterm,termion,termwiz", mapping = { "windows" = "--features=all-widgets,macros,serde,crossterm,termwiz" } } [tasks.default] alias = "ci" diff --git a/src/backend/crossterm.rs b/src/backend/crossterm.rs index 958fcdc8..77372229 100644 --- a/src/backend/crossterm.rs +++ b/src/backend/crossterm.rs @@ -4,12 +4,14 @@ //! [Crossterm]: https://crates.io/crates/crossterm use std::io::{self, Write}; +#[cfg(feature = "underline-color")] +use crossterm::style::SetUnderlineColor; use crossterm::{ cursor::{Hide, MoveTo, Show}, execute, queue, style::{ Attribute as CAttribute, Color as CColor, Print, SetAttribute, SetBackgroundColor, - SetForegroundColor, SetUnderlineColor, + SetForegroundColor, }, terminal::{self, Clear}, }; @@ -124,6 +126,7 @@ where { let mut fg = Color::Reset; let mut bg = Color::Reset; + #[cfg(feature = "underline-color")] let mut underline_color = Color::Reset; let mut modifier = Modifier::empty(); let mut last_pos: Option<(u16, u16)> = None; @@ -151,6 +154,7 @@ where queue!(self.writer, SetBackgroundColor(color))?; bg = cell.bg; } + #[cfg(feature = "underline-color")] if cell.underline_color != underline_color { let color = CColor::from(cell.underline_color); queue!(self.writer, SetUnderlineColor(color))?; @@ -160,13 +164,21 @@ where queue!(self.writer, Print(&cell.symbol))?; } - queue!( + #[cfg(feature = "underline-color")] + return queue!( self.writer, SetForegroundColor(CColor::Reset), SetBackgroundColor(CColor::Reset), SetUnderlineColor(CColor::Reset), - SetAttribute(CAttribute::Reset) - ) + SetAttribute(CAttribute::Reset), + ); + #[cfg(not(feature = "underline-color"))] + return queue!( + self.writer, + SetForegroundColor(CColor::Reset), + SetBackgroundColor(CColor::Reset), + SetAttribute(CAttribute::Reset), + ); } fn hide_cursor(&mut self) -> io::Result<()> { diff --git a/src/buffer.rs b/src/buffer.rs index fa86fba0..b87881b5 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -19,7 +19,7 @@ pub struct Cell { pub symbol: String, pub fg: Color, pub bg: Color, - #[cfg(feature = "crossterm")] + #[cfg(feature = "underline-color")] pub underline_color: Color, pub modifier: Modifier, pub skip: bool, @@ -55,7 +55,7 @@ impl Cell { if let Some(c) = style.bg { self.bg = c; } - #[cfg(feature = "crossterm")] + #[cfg(feature = "underline-color")] if let Some(c) = style.underline_color { self.underline_color = c; } @@ -64,7 +64,7 @@ impl Cell { self } - #[cfg(feature = "crossterm")] + #[cfg(feature = "underline-color")] pub fn style(&self) -> Style { Style::default() .fg(self.fg) @@ -73,7 +73,7 @@ impl Cell { .add_modifier(self.modifier) } - #[cfg(not(feature = "crossterm"))] + #[cfg(not(feature = "underline-color"))] pub fn style(&self) -> Style { Style::default() .fg(self.fg) @@ -95,7 +95,7 @@ impl Cell { self.symbol.push(' '); self.fg = Color::Reset; self.bg = Color::Reset; - #[cfg(feature = "crossterm")] + #[cfg(feature = "underline-color")] { self.underline_color = Color::Reset; } @@ -110,7 +110,7 @@ impl Default for Cell { symbol: " ".into(), fg: Color::Reset, bg: Color::Reset, - #[cfg(feature = "crossterm")] + #[cfg(feature = "underline-color")] underline_color: Color::Reset, modifier: Modifier::empty(), skip: false, @@ -138,7 +138,7 @@ impl Default for Cell { /// symbol: String::from("r"), /// fg: Color::Red, /// bg: Color::White, -/// #[cfg(feature = "crossterm")] +/// #[cfg(feature = "underline-color")] /// underline_color: Color::Reset, /// modifier: Modifier::empty(), /// skip: false @@ -560,7 +560,7 @@ impl Debug for Buffer { overwritten.push((x, &c.symbol)); } skip = std::cmp::max(skip, c.symbol.width()).saturating_sub(1); - #[cfg(feature = "crossterm")] + #[cfg(feature = "underline-color")] { let style = (c.fg, c.bg, c.underline_color, c.modifier); if last_style != Some(style) { @@ -568,7 +568,7 @@ impl Debug for Buffer { styles.push((x, y, c.fg, c.bg, c.underline_color, c.modifier)); } } - #[cfg(not(feature = "crossterm"))] + #[cfg(not(feature = "underline-color"))] { let style = (c.fg, c.bg, c.modifier); if last_style != Some(style) { @@ -586,12 +586,12 @@ impl Debug for Buffer { } f.write_str(" ],\n styles: [\n")?; for s in styles { - #[cfg(feature = "crossterm")] + #[cfg(feature = "underline-color")] f.write_fmt(format_args!( " x: {}, y: {}, fg: {:?}, bg: {:?}, underline: {:?}, modifier: {:?},\n", s.0, s.1, s.2, s.3, s.4, s.5 ))?; - #[cfg(not(feature = "crossterm"))] + #[cfg(not(feature = "underline-color"))] f.write_fmt(format_args!( " x: {}, y: {}, fg: {:?}, bg: {:?}, modifier: {:?},\n", s.0, s.1, s.2, s.3, s.4 @@ -625,7 +625,7 @@ mod tests { .bg(Color::Yellow) .add_modifier(Modifier::BOLD), ); - #[cfg(feature = "crossterm")] + #[cfg(feature = "underline-color")] assert_eq!( format!("{buf:?}"), indoc::indoc!( @@ -643,7 +643,7 @@ mod tests { }" ) ); - #[cfg(not(feature = "crossterm"))] + #[cfg(not(feature = "underline-color"))] assert_eq!( format!("{buf:?}"), indoc::indoc!( diff --git a/src/style.rs b/src/style.rs index 812ea4b6..09b4fc80 100644 --- a/src/style.rs +++ b/src/style.rs @@ -140,7 +140,7 @@ impl fmt::Debug for Modifier { /// let styles = [ /// Style::default().fg(Color::Blue).add_modifier(Modifier::BOLD | Modifier::ITALIC), /// Style::default().bg(Color::Red).add_modifier(Modifier::UNDERLINED), -/// #[cfg(feature = "crossterm")] +/// #[cfg(feature = "underline-color")] /// Style::default().underline_color(Color::Green), /// Style::default().fg(Color::Yellow).remove_modifier(Modifier::ITALIC), /// ]; @@ -152,7 +152,7 @@ impl fmt::Debug for Modifier { /// Style { /// fg: Some(Color::Yellow), /// bg: Some(Color::Red), -/// #[cfg(feature = "crossterm")] +/// #[cfg(feature = "underline-color")] /// underline_color: Some(Color::Green), /// add_modifier: Modifier::BOLD | Modifier::UNDERLINED, /// sub_modifier: Modifier::empty(), @@ -179,7 +179,7 @@ impl fmt::Debug for Modifier { /// Style { /// fg: Some(Color::Yellow), /// bg: Some(Color::Reset), -/// #[cfg(feature = "crossterm")] +/// #[cfg(feature = "underline-color")] /// underline_color: Some(Color::Reset), /// add_modifier: Modifier::empty(), /// sub_modifier: Modifier::empty(), @@ -192,7 +192,7 @@ impl fmt::Debug for Modifier { pub struct Style { pub fg: Option, pub bg: Option, - #[cfg(feature = "crossterm")] + #[cfg(feature = "underline-color")] pub underline_color: Option, pub add_modifier: Modifier, pub sub_modifier: Modifier, @@ -220,7 +220,7 @@ impl Style { Style { fg: None, bg: None, - #[cfg(feature = "crossterm")] + #[cfg(feature = "underline-color")] underline_color: None, add_modifier: Modifier::empty(), sub_modifier: Modifier::empty(), @@ -232,7 +232,7 @@ impl Style { Style { fg: Some(Color::Reset), bg: Some(Color::Reset), - #[cfg(feature = "crossterm")] + #[cfg(feature = "underline-color")] underline_color: Some(Color::Reset), add_modifier: Modifier::empty(), sub_modifier: Modifier::all(), @@ -272,9 +272,12 @@ impl Style { /// Changes the underline color. The text must be underlined with a modifier for this to work. /// /// This uses a non-standard ANSI escape sequence. It is supported by most terminal emulators, - /// but is only implemented in the crossterm backend. + /// but is only implemented in the crossterm backend and enabled by the `underline-color` + /// feature flag. /// - /// See [Wikipedia](https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters) code `58` and `59` for more information. + /// See + /// [Wikipedia](https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters) + /// code `58` and `59` for more information. /// /// ## Examples /// @@ -284,7 +287,7 @@ impl Style { /// let diff = Style::default().underline_color(Color::Red).add_modifier(Modifier::UNDERLINED); /// assert_eq!(style.patch(diff), Style::default().underline_color(Color::Red).add_modifier(Modifier::UNDERLINED)); /// ``` - #[cfg(feature = "crossterm")] + #[cfg(feature = "underline-color")] pub const fn underline_color(mut self, color: Color) -> Style { self.underline_color = Some(color); self @@ -347,7 +350,7 @@ impl Style { self.fg = other.fg.or(self.fg); self.bg = other.bg.or(self.bg); - #[cfg(feature = "crossterm")] + #[cfg(feature = "underline-color")] { self.underline_color = other.underline_color.or(self.underline_color); } diff --git a/src/widgets/gauge.rs b/src/widgets/gauge.rs index 4c4baa29..3926c7ac 100644 --- a/src/widgets/gauge.rs +++ b/src/widgets/gauge.rs @@ -366,7 +366,7 @@ impl<'a> Widget for LineGauge<'a> { .set_style(Style { fg: self.gauge_style.fg, bg: None, - #[cfg(feature = "crossterm")] + #[cfg(feature = "underline-color")] underline_color: self.gauge_style.underline_color, add_modifier: self.gauge_style.add_modifier, sub_modifier: self.gauge_style.sub_modifier, @@ -378,7 +378,7 @@ impl<'a> Widget for LineGauge<'a> { .set_style(Style { fg: self.gauge_style.bg, bg: None, - #[cfg(feature = "crossterm")] + #[cfg(feature = "underline-color")] underline_color: self.gauge_style.underline_color, add_modifier: self.gauge_style.add_modifier, sub_modifier: self.gauge_style.sub_modifier,