on master help sign in
make underline_style a seperate option
71ee589Pascal Kuthe
book/src/themes.md+17−6
@@ -13,10 +13,10 @@ The default theme.toml can be found [here](https://github.com/helix-editor/helix
13Each line in the theme file is specified as below:
13Each line in the theme file is specified as below:
14
14
15```toml
15```toml
16−key = { fg = "#ffffff", bg = "#000000", underline = "#ff0000", modifiers = ["bold", "italic", "undercurled"] }
16+key = { fg = "#ffffff", bg = "#000000", underline_color = "#ff0000", underline_style = "curl", modifiers = ["bold", "italic"] }
17```
17```
18
18
19−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.
19+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.
20
20
21To specify only the foreground color:
21To specify only the foreground color:
22
22
@@ -83,16 +83,27 @@ Less common modifiers might not be supported by your terminal emulator.
83| `dim` |
83| `dim` |
84| `italic` |
84| `italic` |
85| `underlined` |
85| `underlined` |
86−| `undercurled` |
87−| `underdashed` |
88−| `underdotted` |
89−| `double-underlined` |
90| `slow_blink` |
86| `slow_blink` |
91| `rapid_blink` |
87| `rapid_blink` |
92| `reversed` |
88| `reversed` |
93| `hidden` |
89| `hidden` |
94| `crossed_out` |
90| `crossed_out` |
95
91
92+### Underline Style
93+
94+One of the following values may be used as an `underline_styles`.
95+
96+Some styles might not be supported by your terminal emulator.
97+
98+| Modifier |
99+| --- |
100+| `line` |
101+| `curl` |
102+| `dashed` |
103+| `dot` |
104+| `double-line` |
105+
106+
96### Scopes
107### Scopes
97
108
98The following is a list of scopes available to use for styling.
109The following is a list of scopes available to use for styling.
helix-tui/src/backend/crossterm.rs+23−33
@@ -8,7 +8,7 @@ use crossterm::{
8 },
8 },
9 terminal::{self, Clear, ClearType},
9 terminal::{self, Clear, ClearType},
10};
10};
11−use helix_view::graphics::{Color, CursorKind, Modifier, Rect};
11+use helix_view::graphics::{Color, CursorKind, Modifier, Rect, UnderlineStyle};
12use std::io::{self, Write};
12use std::io::{self, Write};
13
13
14fn vte_version() -> Option<usize> {
14fn vte_version() -> Option<usize> {
@@ -80,7 +80,8 @@ where
80 {
80 {
81 let mut fg = Color::Reset;
81 let mut fg = Color::Reset;
82 let mut bg = Color::Reset;
82 let mut bg = Color::Reset;
83− let mut underline = Color::Reset;
83+ let mut underline_color = Color::Reset;
84+ let mut underline_style = UnderlineStyle::Reset;
84 let mut modifier = Modifier::empty();
85 let mut modifier = Modifier::empty();
85 let mut last_pos: Option<(u16, u16)> = None;
86 let mut last_pos: Option<(u16, u16)> = None;
86 for (x, y, cell) in content {
87 for (x, y, cell) in content {
@@ -94,7 +95,7 @@ where
94 from: modifier,
95 from: modifier,
95 to: cell.modifier,
96 to: cell.modifier,
96 };
97 };
97− diff.queue(&mut self.buffer, self.capabilities)?;
98+ diff.queue(&mut self.buffer)?;
98 modifier = cell.modifier;
99 modifier = cell.modifier;
99 }
100 }
100 if cell.fg != fg {
101 if cell.fg != fg {
@@ -107,10 +108,24 @@ where
107 map_error(queue!(self.buffer, SetBackgroundColor(color)))?;
108 map_error(queue!(self.buffer, SetBackgroundColor(color)))?;
108 bg = cell.bg;
109 bg = cell.bg;
109 }
110 }
110− if cell.underline != underline {
111+ if cell.underline_color != underline_color {
111− let color = CColor::from(cell.underline);
112+ let color = CColor::from(cell.underline_color);
112 map_error(queue!(self.buffer, SetUnderlineColor(color)))?;
113 map_error(queue!(self.buffer, SetUnderlineColor(color)))?;
113− underline = cell.underline;
114+ underline_color = cell.underline_color;
115+ }
116+
117+ let mut new_underline_style = cell.underline_style;
118+ if !self.capabilities.has_extended_underlines {
119+ match new_underline_style {
120+ UnderlineStyle::Reset => (),
121+ _ => new_underline_style = UnderlineStyle::Line,
122+ }
123+ }
124+
125+ if new_underline_style != underline_style {
126+ let attr = CAttribute::from(cell.underline_style);
127+ map_error(queue!(self.buffer, SetAttribute(attr)))?;
128+ underline_style = new_underline_style;
114 }
129 }
115
130
116 map_error(queue!(self.buffer, Print(&cell.symbol)))?;
131 map_error(queue!(self.buffer, Print(&cell.symbol)))?;
@@ -118,6 +133,7 @@ where
118
133
119 map_error(queue!(
134 map_error(queue!(
120 self.buffer,
135 self.buffer,
136+ SetUnderlineColor(CColor::Reset),
121 SetForegroundColor(CColor::Reset),
137 SetForegroundColor(CColor::Reset),
122 SetBackgroundColor(CColor::Reset),
138 SetBackgroundColor(CColor::Reset),
123 SetAttribute(CAttribute::Reset)
139 SetAttribute(CAttribute::Reset)
@@ -174,7 +190,7 @@ struct ModifierDiff {
174}
190}
175
191
176impl ModifierDiff {
192impl ModifierDiff {
177− fn queue<W>(&self, mut w: W, caps: Capabilities) -> io::Result<()>
193+ fn queue<W>(&self, mut w: W) -> io::Result<()>
178 where
194 where
179 W: io::Write,
195 W: io::Write,
180 {
196 {
@@ -192,9 +208,6 @@ impl ModifierDiff {
192 if removed.contains(Modifier::ITALIC) {
208 if removed.contains(Modifier::ITALIC) {
193 map_error(queue!(w, SetAttribute(CAttribute::NoItalic)))?;
209 map_error(queue!(w, SetAttribute(CAttribute::NoItalic)))?;
194 }
210 }
195− if removed.intersects(Modifier::ANY_UNDERLINE) {
196− map_error(queue!(w, SetAttribute(CAttribute::NoUnderline)))?;
197− }
198 if removed.contains(Modifier::DIM) {
211 if removed.contains(Modifier::DIM) {
199 map_error(queue!(w, SetAttribute(CAttribute::NormalIntensity)))?;
212 map_error(queue!(w, SetAttribute(CAttribute::NormalIntensity)))?;
200 }
213 }
@@ -205,14 +218,6 @@ impl ModifierDiff {
205 map_error(queue!(w, SetAttribute(CAttribute::NoBlink)))?;
218 map_error(queue!(w, SetAttribute(CAttribute::NoBlink)))?;
206 }
219 }
207
220
208− let queue_styled_underline = |styled_underline, w: &mut W| -> io::Result<()> {
209− let underline = match caps.has_extended_underlines {
210− true => styled_underline,
211− false => CAttribute::Underlined,
212− };
213− map_error(queue!(w, SetAttribute(underline)))
214− };
215−
216 let added = self.to - self.from;
221 let added = self.to - self.from;
217 if added.contains(Modifier::REVERSED) {
222 if added.contains(Modifier::REVERSED) {
218 map_error(queue!(w, SetAttribute(CAttribute::Reverse)))?;
223 map_error(queue!(w, SetAttribute(CAttribute::Reverse)))?;
@@ -223,21 +228,6 @@ impl ModifierDiff {
223 if added.contains(Modifier::ITALIC) {
228 if added.contains(Modifier::ITALIC) {
224 map_error(queue!(w, SetAttribute(CAttribute::Italic)))?;
229 map_error(queue!(w, SetAttribute(CAttribute::Italic)))?;
225 }
230 }
226− if added.contains(Modifier::UNDERLINED) {
227− map_error(queue!(w, SetAttribute(CAttribute::Underlined)))?;
228− }
229− if added.contains(Modifier::UNDERCURLED) {
230− queue_styled_underline(CAttribute::Undercurled, &mut w)?;
231− }
232− if added.contains(Modifier::UNDERDOTTED) {
233− queue_styled_underline(CAttribute::Underdotted, &mut w)?;
234− }
235− if added.contains(Modifier::UNDERDASHED) {
236− queue_styled_underline(CAttribute::Underdashed, &mut w)?;
237− }
238− if added.contains(Modifier::DOUBLE_UNDERLINED) {
239− queue_styled_underline(CAttribute::DoubleUnderlined, &mut w)?;
240− }
241 if added.contains(Modifier::DIM) {
231 if added.contains(Modifier::DIM) {
242 map_error(queue!(w, SetAttribute(CAttribute::Dim)))?;
232 map_error(queue!(w, SetAttribute(CAttribute::Dim)))?;
243 }
233 }
helix-tui/src/buffer.rs+18−9
@@ -3,7 +3,7 @@ use helix_core::unicode::width::UnicodeWidthStr;
3use std::cmp::min;
3use std::cmp::min;
4use unicode_segmentation::UnicodeSegmentation;
4use unicode_segmentation::UnicodeSegmentation;
5
5
6−use helix_view::graphics::{Color, Modifier, Rect, Style};
6+use helix_view::graphics::{Color, Modifier, Rect, Style, UnderlineStyle};
7
7
8/// A buffer cell
8/// A buffer cell
9#[derive(Debug, Clone, PartialEq)]
9#[derive(Debug, Clone, PartialEq)]
@@ -11,7 +11,8 @@ pub struct Cell {
11 pub symbol: String,
11 pub symbol: String,
12 pub fg: Color,
12 pub fg: Color,
13 pub bg: Color,
13 pub bg: Color,
14− pub underline: Color,
14+ pub underline_color: Color,
15+ pub underline_style: UnderlineStyle,
15 pub modifier: Modifier,
16 pub modifier: Modifier,
16}
17}
17
18
@@ -45,9 +46,13 @@ impl Cell {
45 if let Some(c) = style.bg {
46 if let Some(c) = style.bg {
46 self.bg = c;
47 self.bg = c;
47 }
48 }
48− if let Some(c) = style.underline {
49+ if let Some(c) = style.underline_color {
49− self.underline = c;
50+ self.underline_color = c;
50 }
51 }
52+ if let Some(style) = style.underline_style {
53+ self.underline_style = style;
54+ }
55+
51 self.modifier.insert(style.add_modifier);
56 self.modifier.insert(style.add_modifier);
52 self.modifier.remove(style.sub_modifier);
57 self.modifier.remove(style.sub_modifier);
53 self
58 self
@@ -57,7 +62,8 @@ impl Cell {
57 Style::default()
62 Style::default()
58 .fg(self.fg)
63 .fg(self.fg)
59 .bg(self.bg)
64 .bg(self.bg)
60− .underline(self.underline)
65+ .underline_color(self.underline_color)
66+ .underline_style(self.underline_style)
61 .add_modifier(self.modifier)
67 .add_modifier(self.modifier)
62 }
68 }
63
69
@@ -66,7 +72,8 @@ impl Cell {
66 self.symbol.push(' ');
72 self.symbol.push(' ');
67 self.fg = Color::Reset;
73 self.fg = Color::Reset;
68 self.bg = Color::Reset;
74 self.bg = Color::Reset;
69− self.underline = Color::Reset;
75+ self.underline_color = Color::Reset;
76+ self.underline_style = UnderlineStyle::Reset;
70 self.modifier = Modifier::empty();
77 self.modifier = Modifier::empty();
71 }
78 }
72}
79}
@@ -77,7 +84,8 @@ impl Default for Cell {
77 symbol: " ".into(),
84 symbol: " ".into(),
78 fg: Color::Reset,
85 fg: Color::Reset,
79 bg: Color::Reset,
86 bg: Color::Reset,
80− underline: Color::Reset,
87+ underline_color: Color::Reset,
88+ underline_style: UnderlineStyle::Reset,
81 modifier: Modifier::empty(),
89 modifier: Modifier::empty(),
82 }
90 }
83 }
91 }
@@ -94,7 +102,7 @@ impl Default for Cell {
94///
102///
95/// ```
103/// ```
96/// use helix_tui::buffer::{Buffer, Cell};
104/// use helix_tui::buffer::{Buffer, Cell};
97−/// use helix_view::graphics::{Rect, Color, Style, Modifier};
105+/// use helix_view::graphics::{Rect, Color, UnderlineStyle, Style, Modifier};
98///
106///
99/// let mut buf = Buffer::empty(Rect{x: 0, y: 0, width: 10, height: 5});
107/// let mut buf = Buffer::empty(Rect{x: 0, y: 0, width: 10, height: 5});
100/// buf[(0, 2)].set_symbol("x");
108/// buf[(0, 2)].set_symbol("x");
@@ -104,7 +112,8 @@ impl Default for Cell {
104/// symbol: String::from("r"),
112/// symbol: String::from("r"),
105/// fg: Color::Red,
113/// fg: Color::Red,
106/// bg: Color::White,
114/// bg: Color::White,
107−/// underline: Color::Reset,
115+/// underline_color: Color::Reset,
116+/// underline_style: UnderlineStyle::Reset,
108/// modifier: Modifier::empty(),
117/// modifier: Modifier::empty(),
109/// });
118/// });
110/// buf[(5, 0)].set_char('x');
119/// buf[(5, 0)].set_char('x');
helix-tui/src/text.rs+8−4
@@ -134,7 +134,8 @@ impl<'a> Span<'a> {
134 /// style: Style {
134 /// style: Style {
135 /// fg: Some(Color::Yellow),
135 /// fg: Some(Color::Yellow),
136 /// bg: Some(Color::Black),
136 /// bg: Some(Color::Black),
137− /// underline: None,
137+ /// underline_color: None,
138+ /// underline_style: None,
138 /// add_modifier: Modifier::empty(),
139 /// add_modifier: Modifier::empty(),
139 /// sub_modifier: Modifier::empty(),
140 /// sub_modifier: Modifier::empty(),
140 /// },
141 /// },
@@ -144,7 +145,8 @@ impl<'a> Span<'a> {
144 /// style: Style {
145 /// style: Style {
145 /// fg: Some(Color::Yellow),
146 /// fg: Some(Color::Yellow),
146 /// bg: Some(Color::Black),
147 /// bg: Some(Color::Black),
147− /// underline: None,
148+ /// underline_color: None,
149+ /// underline_style: None,
148 /// add_modifier: Modifier::empty(),
150 /// add_modifier: Modifier::empty(),
149 /// sub_modifier: Modifier::empty(),
151 /// sub_modifier: Modifier::empty(),
150 /// },
152 /// },
@@ -154,7 +156,8 @@ impl<'a> Span<'a> {
154 /// style: Style {
156 /// style: Style {
155 /// fg: Some(Color::Yellow),
157 /// fg: Some(Color::Yellow),
156 /// bg: Some(Color::Black),
158 /// bg: Some(Color::Black),
157− /// underline: None,
159+ /// underline_color: None,
160+ /// underline_style: None,
158 /// add_modifier: Modifier::empty(),
161 /// add_modifier: Modifier::empty(),
159 /// sub_modifier: Modifier::empty(),
162 /// sub_modifier: Modifier::empty(),
160 /// },
163 /// },
@@ -164,7 +167,8 @@ impl<'a> Span<'a> {
164 /// style: Style {
167 /// style: Style {
165 /// fg: Some(Color::Yellow),
168 /// fg: Some(Color::Yellow),
166 /// bg: Some(Color::Black),
169 /// bg: Some(Color::Black),
167− /// underline: None,
170+ /// underline_color: None,
171+ /// underline_style: None,
168 /// add_modifier: Modifier::empty(),
172 /// add_modifier: Modifier::empty(),
169 /// sub_modifier: Modifier::empty(),
173 /// sub_modifier: Modifier::empty(),
170 /// },
174 /// },
helix-view/src/graphics.rs+72−29
@@ -315,6 +315,44 @@ impl From<Color> for crossterm::style::Color {
315 }
315 }
316}
316}
317
317
318+#[derive(Debug, Clone, Copy, PartialEq)]
319+pub enum UnderlineStyle {
320+ Reset,
321+ Line,
322+ Curl,
323+ Dotted,
324+ Dashed,
325+ DoubleLine,
326+}
327+
328+impl FromStr for UnderlineStyle {
329+ type Err = &'static str;
330+
331+ fn from_str(modifier: &str) -> Result<Self, Self::Err> {
332+ match modifier {
333+ "line" => Ok(Self::Line),
334+ "curl" => Ok(Self::Curl),
335+ "dotted" => Ok(Self::Dotted),
336+ "dashed" => Ok(Self::Dashed),
337+ "double_line" => Ok(Self::DoubleLine),
338+ _ => Err("Invalid underline style"),
339+ }
340+ }
341+}
342+
343+impl From<UnderlineStyle> for crossterm::style::Attribute {
344+ fn from(style: UnderlineStyle) -> Self {
345+ match style {
346+ UnderlineStyle::Line => crossterm::style::Attribute::Underlined,
347+ UnderlineStyle::Curl => crossterm::style::Attribute::Undercurled,
348+ UnderlineStyle::Dotted => crossterm::style::Attribute::Underdotted,
349+ UnderlineStyle::Dashed => crossterm::style::Attribute::Underdashed,
350+ UnderlineStyle::DoubleLine => crossterm::style::Attribute::DoubleUnderlined,
351+ UnderlineStyle::Reset => crossterm::style::Attribute::NoUnderline,
352+ }
353+ }
354+}
355+
318bitflags! {
356bitflags! {
319 /// Modifier changes the way a piece of text is displayed.
357 /// Modifier changes the way a piece of text is displayed.
320 ///
358 ///
@@ -332,22 +370,11 @@ bitflags! {
332 const BOLD = 0b0000_0000_0000_0001;
370 const BOLD = 0b0000_0000_0000_0001;
333 const DIM = 0b0000_0000_0000_0010;
371 const DIM = 0b0000_0000_0000_0010;
334 const ITALIC = 0b0000_0000_0000_0100;
372 const ITALIC = 0b0000_0000_0000_0100;
335− const UNDERLINED = 0b0000_0000_0000_1000;
336 const SLOW_BLINK = 0b0000_0000_0001_0000;
373 const SLOW_BLINK = 0b0000_0000_0001_0000;
337 const RAPID_BLINK = 0b0000_0000_0010_0000;
374 const RAPID_BLINK = 0b0000_0000_0010_0000;
338 const REVERSED = 0b0000_0000_0100_0000;
375 const REVERSED = 0b0000_0000_0100_0000;
339 const HIDDEN = 0b0000_0000_1000_0000;
376 const HIDDEN = 0b0000_0000_1000_0000;
340 const CROSSED_OUT = 0b0000_0001_0000_0000;
377 const CROSSED_OUT = 0b0000_0001_0000_0000;
341− const UNDERCURLED = 0b0000_0010_0000_0000;
342− const UNDERDOTTED = 0b0000_0100_0000_0000;
343− const UNDERDASHED = 0b0000_1000_0000_0000;
344− const DOUBLE_UNDERLINED = 0b0001_0000_0000_0000;
345−
346− const ANY_UNDERLINE = Self::UNDERLINED.bits
347− | Self::UNDERCURLED.bits
348− | Self::UNDERDOTTED.bits
349− | Self::UNDERDASHED.bits
350− | Self::DOUBLE_UNDERLINED.bits;
351 }
378 }
352}
379}
353
380
@@ -359,16 +386,11 @@ impl FromStr for Modifier {
359 "bold" => Ok(Self::BOLD),
386 "bold" => Ok(Self::BOLD),
360 "dim" => Ok(Self::DIM),
387 "dim" => Ok(Self::DIM),
361 "italic" => Ok(Self::ITALIC),
388 "italic" => Ok(Self::ITALIC),
362− "underlined" => Ok(Self::UNDERLINED),
363 "slow_blink" => Ok(Self::SLOW_BLINK),
389 "slow_blink" => Ok(Self::SLOW_BLINK),
364 "rapid_blink" => Ok(Self::RAPID_BLINK),
390 "rapid_blink" => Ok(Self::RAPID_BLINK),
365 "reversed" => Ok(Self::REVERSED),
391 "reversed" => Ok(Self::REVERSED),
366 "hidden" => Ok(Self::HIDDEN),
392 "hidden" => Ok(Self::HIDDEN),
367 "crossed_out" => Ok(Self::CROSSED_OUT),
393 "crossed_out" => Ok(Self::CROSSED_OUT),
368− "undercurled" => Ok(Self::UNDERCURLED),
369− "underdotted" => Ok(Self::UNDERDOTTED),
370− "underdashed" => Ok(Self::UNDERDASHED),
371− "double_underlined" => Ok(Self::DOUBLE_UNDERLINED),
372 _ => Err("Invalid modifier"),
394 _ => Err("Invalid modifier"),
373 }
395 }
374 }
396 }
@@ -389,7 +411,7 @@ impl FromStr for Modifier {
389/// just S3.
411/// just S3.
390///
412///
391/// ```rust
413/// ```rust
392−/// # use helix_view::graphics::{Rect, Color, Modifier, Style};
414+/// # use helix_view::graphics::{Rect, Color, UnderlineStyle, Modifier, Style};
393/// # use helix_tui::buffer::Buffer;
415/// # use helix_tui::buffer::Buffer;
394/// let styles = [
416/// let styles = [
395/// Style::default().fg(Color::Blue).add_modifier(Modifier::BOLD | Modifier::ITALIC),
417/// Style::default().fg(Color::Blue).add_modifier(Modifier::BOLD | Modifier::ITALIC),
@@ -405,7 +427,8 @@ impl FromStr for Modifier {
405/// fg: Some(Color::Yellow),
427/// fg: Some(Color::Yellow),
406/// bg: Some(Color::Red),
428/// bg: Some(Color::Red),
407/// add_modifier: Modifier::BOLD,
429/// add_modifier: Modifier::BOLD,
408−/// underline: Some(Color::Reset),
430+/// underline_color: Some(Color::Reset),
431+/// underline_style: Some(UnderlineStyle::Reset),
409/// sub_modifier: Modifier::empty(),
432/// sub_modifier: Modifier::empty(),
410/// },
433/// },
411/// buffer[(0, 0)].style(),
434/// buffer[(0, 0)].style(),
@@ -416,7 +439,7 @@ impl FromStr for Modifier {
416/// reset all properties until that point use [`Style::reset`].
439/// reset all properties until that point use [`Style::reset`].
417///
440///
418/// ```
441/// ```
419−/// # use helix_view::graphics::{Rect, Color, Modifier, Style};
442+/// # use helix_view::graphics::{Rect, Color, UnderlineStyle, Modifier, Style};
420/// # use helix_tui::buffer::Buffer;
443/// # use helix_tui::buffer::Buffer;
421/// let styles = [
444/// let styles = [
422/// Style::default().fg(Color::Blue).add_modifier(Modifier::BOLD | Modifier::ITALIC),
445/// Style::default().fg(Color::Blue).add_modifier(Modifier::BOLD | Modifier::ITALIC),
@@ -430,7 +453,8 @@ impl FromStr for Modifier {
430/// Style {
453/// Style {
431/// fg: Some(Color::Yellow),
454/// fg: Some(Color::Yellow),
432/// bg: Some(Color::Reset),
455/// bg: Some(Color::Reset),
433−/// underline: Some(Color::Reset),
456+/// underline_color: Some(Color::Reset),
457+/// underline_style: Some(UnderlineStyle::Reset),
434/// add_modifier: Modifier::empty(),
458/// add_modifier: Modifier::empty(),
435/// sub_modifier: Modifier::empty(),
459/// sub_modifier: Modifier::empty(),
436/// },
460/// },
@@ -442,7 +466,8 @@ impl FromStr for Modifier {
442pub struct Style {
466pub struct Style {
443 pub fg: Option<Color>,
467 pub fg: Option<Color>,
444 pub bg: Option<Color>,
468 pub bg: Option<Color>,
445− pub underline: Option<Color>,
469+ pub underline_color: Option<Color>,
470+ pub underline_style: Option<UnderlineStyle>,
446 pub add_modifier: Modifier,
471 pub add_modifier: Modifier,
447 pub sub_modifier: Modifier,
472 pub sub_modifier: Modifier,
448}
473}
@@ -452,7 +477,8 @@ impl Default for Style {
452 Style {
477 Style {
453 fg: None,
478 fg: None,
454 bg: None,
479 bg: None,
455− underline: None,
480+ underline_color: None,
481+ underline_style: None,
456 add_modifier: Modifier::empty(),
482 add_modifier: Modifier::empty(),
457 sub_modifier: Modifier::empty(),
483 sub_modifier: Modifier::empty(),
458 }
484 }
@@ -465,7 +491,8 @@ impl Style {
465 Style {
491 Style {
466 fg: Some(Color::Reset),
492 fg: Some(Color::Reset),
467 bg: Some(Color::Reset),
493 bg: Some(Color::Reset),
468− underline: Some(Color::Reset),
494+ underline_color: None,
495+ underline_style: None,
469 add_modifier: Modifier::empty(),
496 add_modifier: Modifier::empty(),
470 sub_modifier: Modifier::all(),
497 sub_modifier: Modifier::all(),
471 }
498 }
@@ -507,12 +534,27 @@ impl Style {
507 ///
534 ///
508 /// ```rust
535 /// ```rust
509 /// # use helix_view::graphics::{Color, Style};
536 /// # use helix_view::graphics::{Color, Style};
510− /// let style = Style::default().underline(Color::Blue);
537+ /// let style = Style::default().underline_color(Color::Blue);
511− /// let diff = Style::default().underline(Color::Red);
538+ /// let diff = Style::default().underline_color(Color::Red);
512− /// assert_eq!(style.patch(diff), Style::default().underline(Color::Red));
539+ /// assert_eq!(style.patch(diff), Style::default().underline_color(Color::Red));
540+ /// ```
541+ pub fn underline_color(mut self, color: Color) -> Style {
542+ self.underline_color = Some(color);
543+ self
544+ }
545+
546+ /// Changes the underline style.
547+ ///
548+ /// ## Examples
549+ ///
550+ /// ```rust
551+ /// # use helix_view::graphics::{UnderlineStyle, Style};
552+ /// let style = Style::default().underline_style(UnderlineStyle::Line);
553+ /// let diff = Style::default().underline_style(UnderlineStyle::Curl);
554+ /// assert_eq!(style.patch(diff), Style::default().underline_style(UnderlineStyle::Curl));
513 /// ```
555 /// ```
514− pub fn underline(mut self, color: Color) -> Style {
556+ pub fn underline_style(mut self, style: UnderlineStyle) -> Style {
515− self.underline = Some(color);
557+ self.underline_style = Some(style);
516 self
558 self
517 }
559 }
518
560
@@ -572,7 +614,8 @@ impl Style {
572 pub fn patch(mut self, other: Style) -> Style {
614 pub fn patch(mut self, other: Style) -> Style {
573 self.fg = other.fg.or(self.fg);
615 self.fg = other.fg.or(self.fg);
574 self.bg = other.bg.or(self.bg);
616 self.bg = other.bg.or(self.bg);
575− self.underline = other.underline.or(self.underline);
617+ self.underline_color = other.underline_color.or(self.underline_color);
618+ self.underline_style = other.underline_style.or(self.underline_style);
576
619
577 self.add_modifier.remove(other.sub_modifier);
620 self.add_modifier.remove(other.sub_modifier);
578 self.add_modifier.insert(other.add_modifier);
621 self.add_modifier.insert(other.add_modifier);
helix-view/src/gutter.rs+2−2
@@ -1,7 +1,7 @@
1use std::fmt::Write;
1use std::fmt::Write;
2
2
3use crate::{
3use crate::{
4− graphics::{Color, Modifier, Style},
4+ graphics::{Color, Style, UnderlineStyle},
5 Document, Editor, Theme, View,
5 Document, Editor, Theme, View,
6};
6};
7
7
@@ -147,7 +147,7 @@ pub fn breakpoints<'doc>(
147 .find(|breakpoint| breakpoint.line == line)?;
147 .find(|breakpoint| breakpoint.line == line)?;
148
148
149 let mut style = if breakpoint.condition.is_some() && breakpoint.log_message.is_some() {
149 let mut style = if breakpoint.condition.is_some() && breakpoint.log_message.is_some() {
150− error.add_modifier(Modifier::UNDERLINED)
150+ error.underline_style(UnderlineStyle::Line)
151 } else if breakpoint.condition.is_some() {
151 } else if breakpoint.condition.is_some() {
152 error
152 error
153 } else if breakpoint.log_message.is_some() {
153 } else if breakpoint.log_message.is_some() {
helix-view/src/theme.rs+21−2
@@ -10,6 +10,7 @@ use once_cell::sync::Lazy;
10use serde::{Deserialize, Deserializer};
10use serde::{Deserialize, Deserializer};
11use toml::Value;
11use toml::Value;
12
12
13+use crate::graphics::UnderlineStyle;
13pub use crate::graphics::{Color, Modifier, Style};
14pub use crate::graphics::{Color, Modifier, Style};
14
15
15pub static DEFAULT_THEME: Lazy<Theme> = Lazy::new(|| {
16pub static DEFAULT_THEME: Lazy<Theme> = Lazy::new(|| {
@@ -263,20 +264,38 @@ impl ThemePalette {
263 .ok_or(format!("Theme: invalid modifier: {}", value))
264 .ok_or(format!("Theme: invalid modifier: {}", value))
264 }
265 }
265
266
267+ pub fn parse_underline_style(value: &Value) -> Result<UnderlineStyle, String> {
268+ value
269+ .as_str()
270+ .and_then(|s| s.parse().ok())
271+ .ok_or(format!("Theme: invalid underline_style: {}", value))
272+ }
273+
266 pub fn parse_style(&self, style: &mut Style, value: Value) -> Result<(), String> {
274 pub fn parse_style(&self, style: &mut Style, value: Value) -> Result<(), String> {
267 if let Value::Table(entries) = value {
275 if let Value::Table(entries) = value {
268 for (name, value) in entries {
276 for (name, value) in entries {
269 match name.as_str() {
277 match name.as_str() {
270 "fg" => *style = style.fg(self.parse_color(value)?),
278 "fg" => *style = style.fg(self.parse_color(value)?),
271 "bg" => *style = style.bg(self.parse_color(value)?),
279 "bg" => *style = style.bg(self.parse_color(value)?),
272− "underline" => *style = style.underline(self.parse_color(value)?),
280+ "underline_color" => *style = style.underline_color(self.parse_color(value)?),
281+ "underline_style" => {
282+ warn!("found style");
283+ *style = style.underline_style(Self::parse_underline_style(&value)?)
284+ }
273 "modifiers" => {
285 "modifiers" => {
274 let modifiers = value
286 let modifiers = value
275 .as_array()
287 .as_array()
276 .ok_or("Theme: modifiers should be an array")?;
288 .ok_or("Theme: modifiers should be an array")?;
277
289
278 for modifier in modifiers {
290 for modifier in modifiers {
291+ if modifier
292+ .as_str()
293+ .map_or(false, |modifier| modifier == "underlined")
294+ {
295+ *style = style.underline_style(UnderlineStyle::Line);
296+ } else {
279− *style = style.add_modifier(Self::parse_modifier(modifier)?);
297+ *style = style.add_modifier(Self::parse_modifier(modifier)?);
298+ }
280 }
299 }
281 }
300 }
282 _ => return Err(format!("Theme: invalid style attribute: {}", name)),
301 _ => return Err(format!("Theme: invalid style attribute: {}", name)),
runtime/themes/dark_plus.toml+2−2
@@ -92,8 +92,8 @@
92"info" = { fg = "light_blue" }
92"info" = { fg = "light_blue" }
93"hint" = { fg = "light_gray3" }
93"hint" = { fg = "light_gray3" }
94
94
95−"diagnostic.error" = {underline = "red", modifiers = ["undercurled"] }
95+"diagnostic.error" = {underline_color = "red", underline_style = "curl"}
96−"diagnostic" = {underline = "gold", modifiers = ["undercurled"] }
96+"diagnostic" = {underline_color = "gold", underline_style = "curl" }
97
97
98[palette]
98[palette]
99white = "#ffffff"
99white = "#ffffff"
runtime/themes/onedark.toml+4−4
@@ -39,10 +39,10 @@
39"diff.delta" = "gold"
39"diff.delta" = "gold"
40"diff.minus" = "red"
40"diff.minus" = "red"
41
41
42−"diagnostic.info" = { underline = "blue", modifiers = ["undercurled"] }
42+"diagnostic.info" = { underline_color = "blue", underline_style = "curl" }
43−"diagnostic.hint" = { underline = "green", modifiers = ["undercurled"] }
43+"diagnostic.hint" = { underline_color = "green", underline_style = "curl" }
44−"diagnostic.warning" = { underline = "yellow", modifiers = ["undercurled"] }
44+"diagnostic.warning" = { underline_color = "yellow", underline_style = "curl" }
45−"diagnostic.error" = { underline = "red", modifiers = ["undercurled"] }
45+"diagnostic.error" = { underline_color = "red", underline_style = "curl" }
46"info" = { fg = "blue", modifiers = ["bold"] }
46"info" = { fg = "blue", modifiers = ["bold"] }
47"hint" = { fg = "green", modifiers = ["bold"] }
47"hint" = { fg = "green", modifiers = ["bold"] }
48"warning" = { fg = "yellow", modifiers = ["bold"] }
48"warning" = { fg = "yellow", modifiers = ["bold"] }