mirror of
https://github.com/helix-editor/helix.git
synced 2024-11-25 02:46:17 +04:00
Merge pull request #4061 from pascalkuthe/undercurl-modifier
Support different kinds of underline rendering (updated)
This commit is contained in:
commit
418a622db9
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -508,6 +508,7 @@ dependencies = [
|
|||||||
"helix-core",
|
"helix-core",
|
||||||
"helix-view",
|
"helix-view",
|
||||||
"serde",
|
"serde",
|
||||||
|
"termini",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1100,6 +1101,15 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termini"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "394766021ef3dae8077f080518cdf5360831990f77f5708d5e3594c9b3efa2f9"
|
||||||
|
dependencies = [
|
||||||
|
"dirs-next",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "textwrap"
|
name = "textwrap"
|
||||||
version = "0.15.1"
|
version = "0.15.1"
|
||||||
|
@ -13,10 +13,10 @@ ## Creating a theme
|
|||||||
Each line in the theme file is specified as below:
|
Each line in the theme file is specified as below:
|
||||||
|
|
||||||
```toml
|
```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:
|
To specify only the foreground color:
|
||||||
|
|
||||||
@ -89,6 +89,24 @@ ### Modifiers
|
|||||||
| `hidden` |
|
| `hidden` |
|
||||||
| `crossed_out` |
|
| `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 |
|
||||||
|
| --- |
|
||||||
|
| `line` |
|
||||||
|
| `curl` |
|
||||||
|
| `dashed` |
|
||||||
|
| `dot` |
|
||||||
|
| `double_line` |
|
||||||
|
|
||||||
|
|
||||||
### Inheritance
|
### Inheritance
|
||||||
|
|
||||||
Extend upon other themes by setting the `inherits` property to an existing theme.
|
Extend upon other themes by setting the `inherits` property to an existing theme.
|
||||||
|
@ -20,6 +20,7 @@ bitflags = "1.3"
|
|||||||
cassowary = "0.3"
|
cassowary = "0.3"
|
||||||
unicode-segmentation = "1.10"
|
unicode-segmentation = "1.10"
|
||||||
crossterm = { version = "0.25", optional = true }
|
crossterm = { version = "0.25", optional = true }
|
||||||
|
termini = "0.1"
|
||||||
serde = { version = "1", "optional" = true, features = ["derive"]}
|
serde = { version = "1", "optional" = true, features = ["derive"]}
|
||||||
helix-view = { version = "0.6", path = "../helix-view", features = ["term"] }
|
helix-view = { version = "0.6", path = "../helix-view", features = ["term"] }
|
||||||
helix-core = { version = "0.6", path = "../helix-core" }
|
helix-core = { version = "0.6", path = "../helix-core" }
|
||||||
|
@ -7,12 +7,45 @@
|
|||||||
SetForegroundColor,
|
SetForegroundColor,
|
||||||
},
|
},
|
||||||
terminal::{self, Clear, ClearType},
|
terminal::{self, Clear, ClearType},
|
||||||
|
Command,
|
||||||
};
|
};
|
||||||
use helix_view::graphics::{Color, CursorKind, Modifier, Rect};
|
use helix_view::graphics::{Color, CursorKind, Modifier, Rect, UnderlineStyle};
|
||||||
use std::io::{self, Write};
|
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> {
|
pub struct CrosstermBackend<W: Write> {
|
||||||
buffer: W,
|
buffer: W,
|
||||||
|
capabilities: Capabilities,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<W> CrosstermBackend<W>
|
impl<W> CrosstermBackend<W>
|
||||||
@ -20,7 +53,10 @@ impl<W> CrosstermBackend<W>
|
|||||||
W: Write,
|
W: Write,
|
||||||
{
|
{
|
||||||
pub fn new(buffer: W) -> CrosstermBackend<W> {
|
pub fn new(buffer: W) -> CrosstermBackend<W> {
|
||||||
CrosstermBackend { buffer }
|
CrosstermBackend {
|
||||||
|
buffer,
|
||||||
|
capabilities: Capabilities::from_env_or_default(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,6 +83,8 @@ fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
|
|||||||
{
|
{
|
||||||
let mut fg = Color::Reset;
|
let mut fg = Color::Reset;
|
||||||
let mut bg = 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 modifier = Modifier::empty();
|
||||||
let mut last_pos: Option<(u16, u16)> = None;
|
let mut last_pos: Option<(u16, u16)> = None;
|
||||||
for (x, y, cell) in content {
|
for (x, y, cell) in content {
|
||||||
@ -74,11 +112,32 @@ fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
|
|||||||
bg = cell.bg;
|
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, Print(&cell.symbol)))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
map_error(queue!(
|
map_error(queue!(
|
||||||
self.buffer,
|
self.buffer,
|
||||||
|
SetUnderlineColor(CColor::Reset),
|
||||||
SetForegroundColor(CColor::Reset),
|
SetForegroundColor(CColor::Reset),
|
||||||
SetBackgroundColor(CColor::Reset),
|
SetBackgroundColor(CColor::Reset),
|
||||||
SetAttribute(CAttribute::Reset)
|
SetAttribute(CAttribute::Reset)
|
||||||
@ -153,9 +212,6 @@ fn queue<W>(&self, mut w: W) -> io::Result<()>
|
|||||||
if removed.contains(Modifier::ITALIC) {
|
if removed.contains(Modifier::ITALIC) {
|
||||||
map_error(queue!(w, SetAttribute(CAttribute::NoItalic)))?;
|
map_error(queue!(w, SetAttribute(CAttribute::NoItalic)))?;
|
||||||
}
|
}
|
||||||
if removed.contains(Modifier::UNDERLINED) {
|
|
||||||
map_error(queue!(w, SetAttribute(CAttribute::NoUnderline)))?;
|
|
||||||
}
|
|
||||||
if removed.contains(Modifier::DIM) {
|
if removed.contains(Modifier::DIM) {
|
||||||
map_error(queue!(w, SetAttribute(CAttribute::NormalIntensity)))?;
|
map_error(queue!(w, SetAttribute(CAttribute::NormalIntensity)))?;
|
||||||
}
|
}
|
||||||
@ -176,9 +232,6 @@ fn queue<W>(&self, mut w: W) -> io::Result<()>
|
|||||||
if added.contains(Modifier::ITALIC) {
|
if added.contains(Modifier::ITALIC) {
|
||||||
map_error(queue!(w, SetAttribute(CAttribute::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) {
|
if added.contains(Modifier::DIM) {
|
||||||
map_error(queue!(w, SetAttribute(CAttribute::Dim)))?;
|
map_error(queue!(w, SetAttribute(CAttribute::Dim)))?;
|
||||||
}
|
}
|
||||||
@ -195,3 +248,58 @@ fn queue<W>(&self, mut w: W) -> io::Result<()>
|
|||||||
Ok(())
|
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 std::cmp::min;
|
use std::cmp::min;
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
use helix_view::graphics::{Color, Modifier, Rect, Style};
|
use helix_view::graphics::{Color, Modifier, Rect, Style, UnderlineStyle};
|
||||||
|
|
||||||
/// A buffer cell
|
/// A buffer cell
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
@ -11,6 +11,8 @@ pub struct Cell {
|
|||||||
pub symbol: String,
|
pub symbol: String,
|
||||||
pub fg: Color,
|
pub fg: Color,
|
||||||
pub bg: Color,
|
pub bg: Color,
|
||||||
|
pub underline_color: Color,
|
||||||
|
pub underline_style: UnderlineStyle,
|
||||||
pub modifier: Modifier,
|
pub modifier: Modifier,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,6 +46,13 @@ pub fn set_style(&mut self, style: Style) -> &mut Cell {
|
|||||||
if let Some(c) = style.bg {
|
if let Some(c) = style.bg {
|
||||||
self.bg = c;
|
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.insert(style.add_modifier);
|
||||||
self.modifier.remove(style.sub_modifier);
|
self.modifier.remove(style.sub_modifier);
|
||||||
self
|
self
|
||||||
@ -53,6 +62,8 @@ pub fn style(&self) -> Style {
|
|||||||
Style::default()
|
Style::default()
|
||||||
.fg(self.fg)
|
.fg(self.fg)
|
||||||
.bg(self.bg)
|
.bg(self.bg)
|
||||||
|
.underline_color(self.underline_color)
|
||||||
|
.underline_style(self.underline_style)
|
||||||
.add_modifier(self.modifier)
|
.add_modifier(self.modifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,6 +72,8 @@ pub fn reset(&mut self) {
|
|||||||
self.symbol.push(' ');
|
self.symbol.push(' ');
|
||||||
self.fg = Color::Reset;
|
self.fg = Color::Reset;
|
||||||
self.bg = Color::Reset;
|
self.bg = Color::Reset;
|
||||||
|
self.underline_color = Color::Reset;
|
||||||
|
self.underline_style = UnderlineStyle::Reset;
|
||||||
self.modifier = Modifier::empty();
|
self.modifier = Modifier::empty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -71,6 +84,8 @@ fn default() -> Cell {
|
|||||||
symbol: " ".into(),
|
symbol: " ".into(),
|
||||||
fg: Color::Reset,
|
fg: Color::Reset,
|
||||||
bg: Color::Reset,
|
bg: Color::Reset,
|
||||||
|
underline_color: Color::Reset,
|
||||||
|
underline_style: UnderlineStyle::Reset,
|
||||||
modifier: Modifier::empty(),
|
modifier: Modifier::empty(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -87,7 +102,7 @@ fn default() -> Cell {
|
|||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use helix_tui::buffer::{Buffer, 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});
|
/// let mut buf = Buffer::empty(Rect{x: 0, y: 0, width: 10, height: 5});
|
||||||
/// buf[(0, 2)].set_symbol("x");
|
/// buf[(0, 2)].set_symbol("x");
|
||||||
@ -97,7 +112,9 @@ fn default() -> Cell {
|
|||||||
/// symbol: String::from("r"),
|
/// symbol: String::from("r"),
|
||||||
/// fg: Color::Red,
|
/// fg: Color::Red,
|
||||||
/// bg: Color::White,
|
/// bg: Color::White,
|
||||||
/// modifier: Modifier::empty()
|
/// underline_color: Color::Reset,
|
||||||
|
/// underline_style: UnderlineStyle::Reset,
|
||||||
|
/// modifier: Modifier::empty(),
|
||||||
/// });
|
/// });
|
||||||
/// buf[(5, 0)].set_char('x');
|
/// buf[(5, 0)].set_char('x');
|
||||||
/// assert_eq!(buf[(5, 0)].symbol, "x");
|
/// assert_eq!(buf[(5, 0)].symbol, "x");
|
||||||
|
@ -134,6 +134,8 @@ pub fn width(&self) -> usize {
|
|||||||
/// style: Style {
|
/// style: Style {
|
||||||
/// fg: Some(Color::Yellow),
|
/// fg: Some(Color::Yellow),
|
||||||
/// bg: Some(Color::Black),
|
/// bg: Some(Color::Black),
|
||||||
|
/// underline_color: None,
|
||||||
|
/// underline_style: None,
|
||||||
/// add_modifier: Modifier::empty(),
|
/// add_modifier: Modifier::empty(),
|
||||||
/// sub_modifier: Modifier::empty(),
|
/// sub_modifier: Modifier::empty(),
|
||||||
/// },
|
/// },
|
||||||
@ -143,6 +145,8 @@ pub fn width(&self) -> usize {
|
|||||||
/// style: Style {
|
/// style: Style {
|
||||||
/// fg: Some(Color::Yellow),
|
/// fg: Some(Color::Yellow),
|
||||||
/// bg: Some(Color::Black),
|
/// bg: Some(Color::Black),
|
||||||
|
/// underline_color: None,
|
||||||
|
/// underline_style: None,
|
||||||
/// add_modifier: Modifier::empty(),
|
/// add_modifier: Modifier::empty(),
|
||||||
/// sub_modifier: Modifier::empty(),
|
/// sub_modifier: Modifier::empty(),
|
||||||
/// },
|
/// },
|
||||||
@ -152,6 +156,8 @@ pub fn width(&self) -> usize {
|
|||||||
/// style: Style {
|
/// style: Style {
|
||||||
/// fg: Some(Color::Yellow),
|
/// fg: Some(Color::Yellow),
|
||||||
/// bg: Some(Color::Black),
|
/// bg: Some(Color::Black),
|
||||||
|
/// underline_color: None,
|
||||||
|
/// underline_style: None,
|
||||||
/// add_modifier: Modifier::empty(),
|
/// add_modifier: Modifier::empty(),
|
||||||
/// sub_modifier: Modifier::empty(),
|
/// sub_modifier: Modifier::empty(),
|
||||||
/// },
|
/// },
|
||||||
@ -161,6 +167,8 @@ pub fn width(&self) -> usize {
|
|||||||
/// style: Style {
|
/// style: Style {
|
||||||
/// fg: Some(Color::Yellow),
|
/// fg: Some(Color::Yellow),
|
||||||
/// bg: Some(Color::Black),
|
/// bg: Some(Color::Black),
|
||||||
|
/// underline_color: None,
|
||||||
|
/// underline_style: None,
|
||||||
/// add_modifier: Modifier::empty(),
|
/// add_modifier: Modifier::empty(),
|
||||||
/// sub_modifier: Modifier::empty(),
|
/// sub_modifier: Modifier::empty(),
|
||||||
/// },
|
/// },
|
||||||
|
@ -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! {
|
bitflags! {
|
||||||
/// Modifier changes the way a piece of text is displayed.
|
/// Modifier changes the way a piece of text is displayed.
|
||||||
///
|
///
|
||||||
@ -332,7 +370,6 @@ pub struct Modifier: u16 {
|
|||||||
const BOLD = 0b0000_0000_0001;
|
const BOLD = 0b0000_0000_0001;
|
||||||
const DIM = 0b0000_0000_0010;
|
const DIM = 0b0000_0000_0010;
|
||||||
const ITALIC = 0b0000_0000_0100;
|
const ITALIC = 0b0000_0000_0100;
|
||||||
const UNDERLINED = 0b0000_0000_1000;
|
|
||||||
const SLOW_BLINK = 0b0000_0001_0000;
|
const SLOW_BLINK = 0b0000_0001_0000;
|
||||||
const RAPID_BLINK = 0b0000_0010_0000;
|
const RAPID_BLINK = 0b0000_0010_0000;
|
||||||
const REVERSED = 0b0000_0100_0000;
|
const REVERSED = 0b0000_0100_0000;
|
||||||
@ -349,7 +386,6 @@ fn from_str(modifier: &str) -> Result<Self, Self::Err> {
|
|||||||
"bold" => Ok(Self::BOLD),
|
"bold" => Ok(Self::BOLD),
|
||||||
"dim" => Ok(Self::DIM),
|
"dim" => Ok(Self::DIM),
|
||||||
"italic" => Ok(Self::ITALIC),
|
"italic" => Ok(Self::ITALIC),
|
||||||
"underlined" => Ok(Self::UNDERLINED),
|
|
||||||
"slow_blink" => Ok(Self::SLOW_BLINK),
|
"slow_blink" => Ok(Self::SLOW_BLINK),
|
||||||
"rapid_blink" => Ok(Self::RAPID_BLINK),
|
"rapid_blink" => Ok(Self::RAPID_BLINK),
|
||||||
"reversed" => Ok(Self::REVERSED),
|
"reversed" => Ok(Self::REVERSED),
|
||||||
@ -375,7 +411,7 @@ fn from_str(modifier: &str) -> Result<Self, Self::Err> {
|
|||||||
/// just S3.
|
/// just S3.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use helix_view::graphics::{Rect, Color, Modifier, Style};
|
/// # use helix_view::graphics::{Rect, Color, UnderlineStyle, Modifier, Style};
|
||||||
/// # use helix_tui::buffer::Buffer;
|
/// # use helix_tui::buffer::Buffer;
|
||||||
/// let styles = [
|
/// let styles = [
|
||||||
/// Style::default().fg(Color::Blue).add_modifier(Modifier::BOLD | Modifier::ITALIC),
|
/// Style::default().fg(Color::Blue).add_modifier(Modifier::BOLD | Modifier::ITALIC),
|
||||||
@ -391,6 +427,8 @@ fn from_str(modifier: &str) -> Result<Self, Self::Err> {
|
|||||||
/// fg: Some(Color::Yellow),
|
/// fg: Some(Color::Yellow),
|
||||||
/// bg: Some(Color::Red),
|
/// bg: Some(Color::Red),
|
||||||
/// add_modifier: Modifier::BOLD,
|
/// add_modifier: Modifier::BOLD,
|
||||||
|
/// underline_color: Some(Color::Reset),
|
||||||
|
/// underline_style: Some(UnderlineStyle::Reset),
|
||||||
/// sub_modifier: Modifier::empty(),
|
/// sub_modifier: Modifier::empty(),
|
||||||
/// },
|
/// },
|
||||||
/// buffer[(0, 0)].style(),
|
/// buffer[(0, 0)].style(),
|
||||||
@ -401,7 +439,7 @@ fn from_str(modifier: &str) -> Result<Self, Self::Err> {
|
|||||||
/// reset all properties until that point use [`Style::reset`].
|
/// 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;
|
/// # use helix_tui::buffer::Buffer;
|
||||||
/// let styles = [
|
/// let styles = [
|
||||||
/// Style::default().fg(Color::Blue).add_modifier(Modifier::BOLD | Modifier::ITALIC),
|
/// Style::default().fg(Color::Blue).add_modifier(Modifier::BOLD | Modifier::ITALIC),
|
||||||
@ -415,6 +453,8 @@ fn from_str(modifier: &str) -> Result<Self, Self::Err> {
|
|||||||
/// Style {
|
/// Style {
|
||||||
/// fg: Some(Color::Yellow),
|
/// fg: Some(Color::Yellow),
|
||||||
/// bg: Some(Color::Reset),
|
/// bg: Some(Color::Reset),
|
||||||
|
/// underline_color: Some(Color::Reset),
|
||||||
|
/// underline_style: Some(UnderlineStyle::Reset),
|
||||||
/// add_modifier: Modifier::empty(),
|
/// add_modifier: Modifier::empty(),
|
||||||
/// sub_modifier: Modifier::empty(),
|
/// sub_modifier: Modifier::empty(),
|
||||||
/// },
|
/// },
|
||||||
@ -426,6 +466,8 @@ fn from_str(modifier: &str) -> Result<Self, Self::Err> {
|
|||||||
pub struct Style {
|
pub struct Style {
|
||||||
pub fg: Option<Color>,
|
pub fg: Option<Color>,
|
||||||
pub bg: Option<Color>,
|
pub bg: Option<Color>,
|
||||||
|
pub underline_color: Option<Color>,
|
||||||
|
pub underline_style: Option<UnderlineStyle>,
|
||||||
pub add_modifier: Modifier,
|
pub add_modifier: Modifier,
|
||||||
pub sub_modifier: Modifier,
|
pub sub_modifier: Modifier,
|
||||||
}
|
}
|
||||||
@ -435,6 +477,8 @@ fn default() -> Style {
|
|||||||
Style {
|
Style {
|
||||||
fg: None,
|
fg: None,
|
||||||
bg: None,
|
bg: None,
|
||||||
|
underline_color: None,
|
||||||
|
underline_style: None,
|
||||||
add_modifier: Modifier::empty(),
|
add_modifier: Modifier::empty(),
|
||||||
sub_modifier: Modifier::empty(),
|
sub_modifier: Modifier::empty(),
|
||||||
}
|
}
|
||||||
@ -447,6 +491,8 @@ pub fn reset() -> Style {
|
|||||||
Style {
|
Style {
|
||||||
fg: Some(Color::Reset),
|
fg: Some(Color::Reset),
|
||||||
bg: Some(Color::Reset),
|
bg: Some(Color::Reset),
|
||||||
|
underline_color: None,
|
||||||
|
underline_style: None,
|
||||||
add_modifier: Modifier::empty(),
|
add_modifier: Modifier::empty(),
|
||||||
sub_modifier: Modifier::all(),
|
sub_modifier: Modifier::all(),
|
||||||
}
|
}
|
||||||
@ -482,6 +528,36 @@ pub fn bg(mut self, color: Color) -> Style {
|
|||||||
self
|
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.
|
/// Changes the text emphasis.
|
||||||
///
|
///
|
||||||
/// When applied, it adds the given modifier to the `Style` modifiers.
|
/// When applied, it adds the given modifier to the `Style` modifiers.
|
||||||
@ -538,6 +614,8 @@ pub fn remove_modifier(mut self, modifier: Modifier) -> Style {
|
|||||||
pub fn patch(mut self, other: Style) -> Style {
|
pub fn patch(mut self, other: Style) -> Style {
|
||||||
self.fg = other.fg.or(self.fg);
|
self.fg = other.fg.or(self.fg);
|
||||||
self.bg = other.bg.or(self.bg);
|
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.remove(other.sub_modifier);
|
||||||
self.add_modifier.insert(other.add_modifier);
|
self.add_modifier.insert(other.add_modifier);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
graphics::{Color, Modifier, Style},
|
graphics::{Color, Style, UnderlineStyle},
|
||||||
Document, Editor, Theme, View,
|
Document, Editor, Theme, View,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -147,7 +147,7 @@ pub fn breakpoints<'doc>(
|
|||||||
.find(|breakpoint| breakpoint.line == line)?;
|
.find(|breakpoint| breakpoint.line == line)?;
|
||||||
|
|
||||||
let mut style = if breakpoint.condition.is_some() && breakpoint.log_message.is_some() {
|
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() {
|
} else if breakpoint.condition.is_some() {
|
||||||
error
|
error
|
||||||
} else if breakpoint.log_message.is_some() {
|
} else if breakpoint.log_message.is_some() {
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
use serde::{Deserialize, Deserializer};
|
use serde::{Deserialize, Deserializer};
|
||||||
use toml::{map::Map, Value};
|
use toml::{map::Map, Value};
|
||||||
|
|
||||||
|
use crate::graphics::UnderlineStyle;
|
||||||
pub use crate::graphics::{Color, Modifier, Style};
|
pub use crate::graphics::{Color, Modifier, Style};
|
||||||
|
|
||||||
pub static DEFAULT_THEME: Lazy<Theme> = Lazy::new(|| {
|
pub static DEFAULT_THEME: Lazy<Theme> = Lazy::new(|| {
|
||||||
@ -378,21 +379,50 @@ pub fn parse_modifier(value: &Value) -> Result<Modifier, String> {
|
|||||||
.ok_or(format!("Theme: invalid modifier: {}", value))
|
.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> {
|
pub fn parse_style(&self, style: &mut Style, value: Value) -> Result<(), String> {
|
||||||
if let Value::Table(entries) = value {
|
if let Value::Table(entries) = value {
|
||||||
for (name, value) in entries {
|
for (name, mut value) in entries {
|
||||||
match name.as_str() {
|
match name.as_str() {
|
||||||
"fg" => *style = style.fg(self.parse_color(value)?),
|
"fg" => *style = style.fg(self.parse_color(value)?),
|
||||||
"bg" => *style = style.bg(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" => {
|
"modifiers" => {
|
||||||
let modifiers = value
|
let modifiers = value
|
||||||
.as_array()
|
.as_array()
|
||||||
.ok_or("Theme: modifiers should be an array")?;
|
.ok_or("Theme: modifiers should be an array")?;
|
||||||
|
|
||||||
for modifier in modifiers {
|
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)?);
|
*style = style.add_modifier(Self::parse_modifier(modifier)?);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
_ => return Err(format!("Theme: invalid style attribute: {}", name)),
|
_ => return Err(format!("Theme: invalid style attribute: {}", name)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,7 +92,8 @@
|
|||||||
"info" = { fg = "light_blue" }
|
"info" = { fg = "light_blue" }
|
||||||
"hint" = { fg = "light_gray3" }
|
"hint" = { fg = "light_gray3" }
|
||||||
|
|
||||||
diagnostic = { modifiers = ["underlined"] }
|
"diagnostic.error".underline = { color = "red", style = "curl" }
|
||||||
|
"diagnostic".underline = { color = "gold", style = "curl" }
|
||||||
|
|
||||||
[palette]
|
[palette]
|
||||||
white = "#ffffff"
|
white = "#ffffff"
|
||||||
|
@ -39,7 +39,10 @@
|
|||||||
"diff.delta" = "gold"
|
"diff.delta" = "gold"
|
||||||
"diff.minus" = "red"
|
"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"] }
|
"info" = { fg = "blue", modifiers = ["bold"] }
|
||||||
"hint" = { fg = "green", modifiers = ["bold"] }
|
"hint" = { fg = "green", modifiers = ["bold"] }
|
||||||
"warning" = { fg = "yellow", modifiers = ["bold"] }
|
"warning" = { fg = "yellow", modifiers = ["bold"] }
|
||||||
|
Loading…
Reference in New Issue
Block a user