Merge pull request #4061 from pascalkuthe/undercurl-modifier
Support different kinds of underline rendering (updated)
This commit is contained in:
commit
418a622db9
11 changed files with 307 additions and 33 deletions
10
Cargo.lock
generated
10
Cargo.lock
generated
|
@ -508,6 +508,7 @@ dependencies = [
|
|||
"helix-core",
|
||||
"helix-view",
|
||||
"serde",
|
||||
"termini",
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
|
@ -1100,6 +1101,15 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termini"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "394766021ef3dae8077f080518cdf5360831990f77f5708d5e3594c9b3efa2f9"
|
||||
dependencies = [
|
||||
"dirs-next",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.15.1"
|
||||
|
|
|
@ -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 = { color = "#ff0000", style = "curl"}, modifiers = ["bold", "italic"] }
|
||||
```
|
||||
|
||||
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 `style`/`color`, 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,35 @@ 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` |
|
||||
|
||||
> Note: The `underlined` modifier is deprecated and only available for backwards compatibility.
|
||||
> Its behavior is equivalent to setting `underline.style="line"`.
|
||||
|
||||
### Underline Style
|
||||
|
||||
One of the following values may be used as a value for `underline.style`.
|
||||
|
||||
Some styles might not be supported by your terminal emulator.
|
||||
|
||||
| Modifier |
|
||||
| --- |
|
||||
| `bold` |
|
||||
| `dim` |
|
||||
| `italic` |
|
||||
| `underlined` |
|
||||
| `slow_blink` |
|
||||
| `rapid_blink` |
|
||||
| `reversed` |
|
||||
| `hidden` |
|
||||
| `crossed_out` |
|
||||
| `line` |
|
||||
| `curl` |
|
||||
| `dashed` |
|
||||
| `dot` |
|
||||
| `double_line` |
|
||||
|
||||
|
||||
### Inheritance
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ bitflags = "1.3"
|
|||
cassowary = "0.3"
|
||||
unicode-segmentation = "1.10"
|
||||
crossterm = { version = "0.25", optional = true }
|
||||
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" }
|
||||
|
|
|
@ -7,12 +7,45 @@ use crossterm::{
|
|||
SetForegroundColor,
|
||||
},
|
||||
terminal::{self, Clear, ClearType},
|
||||
Command,
|
||||
};
|
||||
use helix_view::graphics::{Color, CursorKind, Modifier, Rect};
|
||||
use std::io::{self, Write};
|
||||
use helix_view::graphics::{Color, CursorKind, Modifier, Rect, UnderlineStyle};
|
||||
use std::{
|
||||
fmt,
|
||||
io::{self, Write},
|
||||
};
|
||||
fn vte_version() -> Option<usize> {
|
||||
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 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.extended_cap("Smulx").is_some()
|
||||
|| t.extended_cap("Su").is_some()
|
||||
|| vte_version() >= Some(5102),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CrosstermBackend<W: Write> {
|
||||
buffer: W,
|
||||
capabilities: Capabilities,
|
||||
}
|
||||
|
||||
impl<W> CrosstermBackend<W>
|
||||
|
@ -20,7 +53,10 @@ where
|
|||
W: Write,
|
||||
{
|
||||
pub fn new(buffer: W) -> CrosstermBackend<W> {
|
||||
CrosstermBackend { buffer }
|
||||
CrosstermBackend {
|
||||
buffer,
|
||||
capabilities: Capabilities::from_env_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,6 +83,8 @@ where
|
|||
{
|
||||
let mut fg = Color::Reset;
|
||||
let mut bg = 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 {
|
||||
|
@ -74,11 +112,32 @@ where
|
|||
bg = cell.bg;
|
||||
}
|
||||
|
||||
let mut new_underline_style = cell.underline_style;
|
||||
if self.capabilities.has_extended_underlines {
|
||||
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;
|
||||
}
|
||||
} else {
|
||||
match new_underline_style {
|
||||
UnderlineStyle::Reset | UnderlineStyle::Line => (),
|
||||
_ => new_underline_style = UnderlineStyle::Line,
|
||||
}
|
||||
}
|
||||
|
||||
if new_underline_style != underline_style {
|
||||
let attr = CAttribute::from(new_underline_style);
|
||||
map_error(queue!(self.buffer, SetAttribute(attr)))?;
|
||||
underline_style = new_underline_style;
|
||||
}
|
||||
|
||||
map_error(queue!(self.buffer, Print(&cell.symbol)))?;
|
||||
}
|
||||
|
||||
map_error(queue!(
|
||||
self.buffer,
|
||||
SetUnderlineColor(CColor::Reset),
|
||||
SetForegroundColor(CColor::Reset),
|
||||
SetBackgroundColor(CColor::Reset),
|
||||
SetAttribute(CAttribute::Reset)
|
||||
|
@ -153,9 +212,6 @@ impl ModifierDiff {
|
|||
if removed.contains(Modifier::ITALIC) {
|
||||
map_error(queue!(w, SetAttribute(CAttribute::NoItalic)))?;
|
||||
}
|
||||
if removed.contains(Modifier::UNDERLINED) {
|
||||
map_error(queue!(w, SetAttribute(CAttribute::NoUnderline)))?;
|
||||
}
|
||||
if removed.contains(Modifier::DIM) {
|
||||
map_error(queue!(w, SetAttribute(CAttribute::NormalIntensity)))?;
|
||||
}
|
||||
|
@ -176,9 +232,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::DIM) {
|
||||
map_error(queue!(w, SetAttribute(CAttribute::Dim)))?;
|
||||
}
|
||||
|
@ -195,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.",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,6 +11,8 @@ pub struct Cell {
|
|||
pub symbol: String,
|
||||
pub fg: Color,
|
||||
pub bg: Color,
|
||||
pub underline_color: Color,
|
||||
pub underline_style: UnderlineStyle,
|
||||
pub modifier: Modifier,
|
||||
}
|
||||
|
||||
|
@ -44,6 +46,13 @@ impl Cell {
|
|||
if let Some(c) = style.bg {
|
||||
self.bg = 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
|
||||
|
@ -53,6 +62,8 @@ impl Cell {
|
|||
Style::default()
|
||||
.fg(self.fg)
|
||||
.bg(self.bg)
|
||||
.underline_color(self.underline_color)
|
||||
.underline_style(self.underline_style)
|
||||
.add_modifier(self.modifier)
|
||||
}
|
||||
|
||||
|
@ -61,6 +72,8 @@ impl Cell {
|
|||
self.symbol.push(' ');
|
||||
self.fg = Color::Reset;
|
||||
self.bg = Color::Reset;
|
||||
self.underline_color = Color::Reset;
|
||||
self.underline_style = UnderlineStyle::Reset;
|
||||
self.modifier = Modifier::empty();
|
||||
}
|
||||
}
|
||||
|
@ -71,6 +84,8 @@ impl Default for Cell {
|
|||
symbol: " ".into(),
|
||||
fg: Color::Reset,
|
||||
bg: Color::Reset,
|
||||
underline_color: Color::Reset,
|
||||
underline_style: UnderlineStyle::Reset,
|
||||
modifier: Modifier::empty(),
|
||||
}
|
||||
}
|
||||
|
@ -87,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");
|
||||
|
@ -97,7 +112,9 @@ impl Default for Cell {
|
|||
/// symbol: String::from("r"),
|
||||
/// fg: Color::Red,
|
||||
/// bg: Color::White,
|
||||
/// modifier: Modifier::empty()
|
||||
/// underline_color: Color::Reset,
|
||||
/// underline_style: UnderlineStyle::Reset,
|
||||
/// modifier: Modifier::empty(),
|
||||
/// });
|
||||
/// buf[(5, 0)].set_char('x');
|
||||
/// assert_eq!(buf[(5, 0)].symbol, "x");
|
||||
|
|
|
@ -134,6 +134,8 @@ impl<'a> Span<'a> {
|
|||
/// style: Style {
|
||||
/// fg: Some(Color::Yellow),
|
||||
/// bg: Some(Color::Black),
|
||||
/// underline_color: None,
|
||||
/// underline_style: None,
|
||||
/// add_modifier: Modifier::empty(),
|
||||
/// sub_modifier: Modifier::empty(),
|
||||
/// },
|
||||
|
@ -143,6 +145,8 @@ impl<'a> Span<'a> {
|
|||
/// style: Style {
|
||||
/// fg: Some(Color::Yellow),
|
||||
/// bg: Some(Color::Black),
|
||||
/// underline_color: None,
|
||||
/// underline_style: None,
|
||||
/// add_modifier: Modifier::empty(),
|
||||
/// sub_modifier: Modifier::empty(),
|
||||
/// },
|
||||
|
@ -152,6 +156,8 @@ impl<'a> Span<'a> {
|
|||
/// style: Style {
|
||||
/// fg: Some(Color::Yellow),
|
||||
/// bg: Some(Color::Black),
|
||||
/// underline_color: None,
|
||||
/// underline_style: None,
|
||||
/// add_modifier: Modifier::empty(),
|
||||
/// sub_modifier: Modifier::empty(),
|
||||
/// },
|
||||
|
@ -161,6 +167,8 @@ impl<'a> Span<'a> {
|
|||
/// style: Style {
|
||||
/// fg: Some(Color::Yellow),
|
||||
/// bg: Some(Color::Black),
|
||||
/// underline_color: None,
|
||||
/// underline_style: None,
|
||||
/// add_modifier: Modifier::empty(),
|
||||
/// sub_modifier: Modifier::empty(),
|
||||
/// },
|
||||
|
|
|
@ -315,6 +315,44 @@ impl From<Color> 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<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,7 +370,6 @@ bitflags! {
|
|||
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;
|
||||
|
@ -349,7 +386,6 @@ 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),
|
||||
|
@ -375,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),
|
||||
|
@ -391,6 +427,8 @@ impl FromStr for Modifier {
|
|||
/// fg: Some(Color::Yellow),
|
||||
/// bg: Some(Color::Red),
|
||||
/// add_modifier: Modifier::BOLD,
|
||||
/// underline_color: Some(Color::Reset),
|
||||
/// underline_style: Some(UnderlineStyle::Reset),
|
||||
/// sub_modifier: Modifier::empty(),
|
||||
/// },
|
||||
/// buffer[(0, 0)].style(),
|
||||
|
@ -401,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),
|
||||
|
@ -415,6 +453,8 @@ impl FromStr for Modifier {
|
|||
/// Style {
|
||||
/// fg: Some(Color::Yellow),
|
||||
/// bg: Some(Color::Reset),
|
||||
/// underline_color: Some(Color::Reset),
|
||||
/// underline_style: Some(UnderlineStyle::Reset),
|
||||
/// add_modifier: Modifier::empty(),
|
||||
/// sub_modifier: Modifier::empty(),
|
||||
/// },
|
||||
|
@ -426,6 +466,8 @@ impl FromStr for Modifier {
|
|||
pub struct Style {
|
||||
pub fg: Option<Color>,
|
||||
pub bg: Option<Color>,
|
||||
pub underline_color: Option<Color>,
|
||||
pub underline_style: Option<UnderlineStyle>,
|
||||
pub add_modifier: Modifier,
|
||||
pub sub_modifier: Modifier,
|
||||
}
|
||||
|
@ -435,6 +477,8 @@ impl Default for Style {
|
|||
Style {
|
||||
fg: None,
|
||||
bg: None,
|
||||
underline_color: None,
|
||||
underline_style: None,
|
||||
add_modifier: Modifier::empty(),
|
||||
sub_modifier: Modifier::empty(),
|
||||
}
|
||||
|
@ -447,6 +491,8 @@ impl Style {
|
|||
Style {
|
||||
fg: Some(Color::Reset),
|
||||
bg: Some(Color::Reset),
|
||||
underline_color: None,
|
||||
underline_style: None,
|
||||
add_modifier: Modifier::empty(),
|
||||
sub_modifier: Modifier::all(),
|
||||
}
|
||||
|
@ -482,6 +528,36 @@ impl Style {
|
|||
self
|
||||
}
|
||||
|
||||
/// Changes the underline color.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use helix_view::graphics::{Color, Style};
|
||||
/// 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_style(mut self, style: UnderlineStyle) -> Style {
|
||||
self.underline_style = Some(style);
|
||||
self
|
||||
}
|
||||
|
||||
/// Changes the text emphasis.
|
||||
///
|
||||
/// When applied, it adds the given modifier to the `Style` modifiers.
|
||||
|
@ -538,6 +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_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);
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -11,6 +11,7 @@ use once_cell::sync::Lazy;
|
|||
use serde::{Deserialize, Deserializer};
|
||||
use toml::{map::Map, Value};
|
||||
|
||||
use crate::graphics::UnderlineStyle;
|
||||
pub use crate::graphics::{Color, Modifier, Style};
|
||||
|
||||
pub static DEFAULT_THEME: Lazy<Theme> = Lazy::new(|| {
|
||||
|
@ -378,19 +379,48 @@ impl ThemePalette {
|
|||
.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 {
|
||||
for (name, mut value) in entries {
|
||||
match name.as_str() {
|
||||
"fg" => *style = style.fg(self.parse_color(value)?),
|
||||
"bg" => *style = style.bg(self.parse_color(value)?),
|
||||
"underline" => {
|
||||
let table = value
|
||||
.as_table_mut()
|
||||
.ok_or("Theme: underline must be table")?;
|
||||
if let Some(value) = table.remove("color") {
|
||||
*style = style.underline_color(self.parse_color(value)?);
|
||||
}
|
||||
if let Some(value) = table.remove("style") {
|
||||
*style = style.underline_style(Self::parse_underline_style(&value)?);
|
||||
}
|
||||
|
||||
if let Some(attr) = table.keys().next() {
|
||||
return Err(format!("Theme: invalid underline attribute: {attr}"));
|
||||
}
|
||||
}
|
||||
"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)),
|
||||
|
|
|
@ -92,7 +92,8 @@
|
|||
"info" = { fg = "light_blue" }
|
||||
"hint" = { fg = "light_gray3" }
|
||||
|
||||
diagnostic = { modifiers = ["underlined"] }
|
||||
"diagnostic.error".underline = { color = "red", style = "curl" }
|
||||
"diagnostic".underline = { color = "gold", style = "curl" }
|
||||
|
||||
[palette]
|
||||
white = "#ffffff"
|
||||
|
|
|
@ -39,7 +39,10 @@
|
|||
"diff.delta" = "gold"
|
||||
"diff.minus" = "red"
|
||||
|
||||
diagnostic = { modifiers = ["underlined"] }
|
||||
"diagnostic.info".underline = { color = "blue", style = "curl" }
|
||||
"diagnostic.hint".underline = { color = "green", style = "curl" }
|
||||
"diagnostic.warning".underline = { color = "yellow", style = "curl" }
|
||||
"diagnostic.error".underline = { color = "red", style = "curl" }
|
||||
"info" = { fg = "blue", modifiers = ["bold"] }
|
||||
"hint" = { fg = "green", modifiers = ["bold"] }
|
||||
"warning" = { fg = "yellow", modifiers = ["bold"] }
|
||||
|
|
Loading…
Reference in a new issue