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.
This commit is contained in:
Pascal Kuthe 2022-10-01 02:15:25 +02:00
parent 79d3d44c3d
commit 71ee589bbc
No known key found for this signature in database
GPG Key ID: D715E8655AE166A6
9 changed files with 167 additions and 91 deletions

View File

@ -13,10 +13,10 @@ ## Creating a theme
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 @@ ### Modifiers
| `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.

View File

@ -8,7 +8,7 @@
},
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<usize> {
@ -80,7 +80,8 @@ fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
{
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 @@ fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
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 @@ fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
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 @@ fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
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<W>(&self, mut w: W, caps: Capabilities) -> io::Result<()>
fn queue<W>(&self, mut w: W) -> io::Result<()>
where
W: io::Write,
{
@ -192,9 +208,6 @@ fn queue<W>(&self, mut w: W, caps: Capabilities) -> io::Result<()>
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 @@ fn queue<W>(&self, mut w: W, caps: Capabilities) -> io::Result<()>
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 @@ fn queue<W>(&self, mut w: W, caps: Capabilities) -> io::Result<()>
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)))?;
}

View File

@ -3,7 +3,7 @@
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 @@ pub fn set_style(&mut self, style: Style) -> &mut 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 @@ pub fn style(&self) -> Style {
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 @@ pub fn reset(&mut self) {
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 @@ fn default() -> 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 @@ fn default() -> 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 @@ fn default() -> 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');

View File

@ -134,7 +134,8 @@ pub fn width(&self) -> usize {
/// 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 @@ pub fn width(&self) -> usize {
/// 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 @@ pub fn width(&self) -> usize {
/// 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 @@ pub fn width(&self) -> usize {
/// 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(),
/// },

View File

@ -315,6 +315,44 @@ fn from(color: Color) -> Self {
}
}
#[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<Self, Self::Err> {
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<UnderlineStyle> 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 @@ pub struct Modifier: u16 {
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 @@ fn from_str(modifier: &str) -> Result<Self, Self::Err> {
"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 @@ fn from_str(modifier: &str) -> Result<Self, Self::Err> {
/// 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 @@ fn from_str(modifier: &str) -> Result<Self, Self::Err> {
/// 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 @@ fn from_str(modifier: &str) -> Result<Self, Self::Err> {
/// 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 @@ fn from_str(modifier: &str) -> Result<Self, Self::Err> {
/// 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 @@ fn from_str(modifier: &str) -> Result<Self, Self::Err> {
pub struct Style {
pub fg: Option<Color>,
pub bg: Option<Color>,
pub underline: Option<Color>,
pub underline_color: Option<Color>,
pub underline_style: Option<UnderlineStyle>,
pub add_modifier: Modifier,
pub sub_modifier: Modifier,
}
@ -452,7 +477,8 @@ fn default() -> 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 @@ pub fn reset() -> 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 @@ pub fn bg(mut self, color: Color) -> 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(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 +614,8 @@ pub fn remove_modifier(mut self, modifier: Modifier) -> 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);

View File

@ -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() {

View File

@ -10,6 +10,7 @@
use serde::{Deserialize, Deserializer};
use toml::Value;
use crate::graphics::UnderlineStyle;
pub use crate::graphics::{Color, Modifier, Style};
pub static DEFAULT_THEME: Lazy<Theme> = Lazy::new(|| {
@ -263,22 +264,40 @@ pub fn parse_modifier(value: &Value) -> Result<Modifier, String> {
.ok_or(format!("Theme: invalid modifier: {}", value))
}
pub fn parse_underline_style(value: &Value) -> Result<UnderlineStyle, String> {
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 {
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)),
}
}

View File

@ -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"

View File

@ -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"] }