@@ -11,8 +11,38 @@ use crossterm::{
1111use helix_view::graphics::{Color, CursorKind, Modifier, Rect};
1212use std::io::{self, Write};
1313
14+fn vte_version() -> Option<usize> {
15+ std::env::var("VTE_VERSION").ok()?.parse().ok()
16+}
17+
18+/// Describes terminal capabilities like extended underline, truecolor, etc.
19+#[derive(Copy, Clone, Debug, Default)]
20+struct Capabilities {
21+ /// Support for undercurled, underdashed, etc.
22+ has_extended_underlines: bool,
23+}
24+
25+impl Capabilities {
26+ /// Detect capabilities from the terminfo database located based
27+ /// on the $TERM environment variable. If detection fails, returns
28+ /// a default value where no capability is supported.
29+ pub fn from_env_or_default() -> Self {
30+ match cxterminfo::terminfo::TermInfo::from_env() {
31+ Err(_) => Capabilities::default(),
32+ Ok(t) => Capabilities {
33+ // Smulx, VTE: https://unix.stackexchange.com/a/696253/246284
34+ // Su (used by kitty): https://sw.kovidgoyal.net/kitty/underlines
35+ has_extended_underlines: t.get_ext_string("Smulx").is_some()
36+ || *t.get_ext_bool("Su").unwrap_or(&false)
37+ || vte_version() >= Some(5102),
38+ },
39+ }
40+ }
41+}
42+
1443pub struct CrosstermBackend<W: Write> {
1544 buffer: W,
45+ capabilities: Capabilities,
1646}
1747
1848impl<W> CrosstermBackend<W>
@@ -20,7 +50,10 @@ where
2050 W: Write,
2151{
2252 pub fn new(buffer: W) -> CrosstermBackend<W> {
23− CrosstermBackend { buffer }
53+ CrosstermBackend {
54+ buffer,
55+ capabilities: Capabilities::from_env_or_default(),
56+ }
2457 }
2558}
2659
@@ -61,7 +94,7 @@ where
6194 from: modifier,
6295 to: cell.modifier,
6396 };
64− diff.queue(&mut self.buffer)?;
97+ diff.queue(&mut self.buffer, self.capabilities)?;
6598 modifier = cell.modifier;
6699 }
67100 if cell.fg != fg {
@@ -141,7 +174,7 @@ struct ModifierDiff {
141174}
142175
143176impl ModifierDiff {
144− fn queue<W>(&self, mut w: W) -> io::Result<()>
177+ fn queue<W>(&self, mut w: W, caps: Capabilities) -> io::Result<()>
145178 where
146179 W: io::Write,
147180 {
@@ -172,6 +205,14 @@ impl ModifierDiff {
172205 map_error(queue!(w, SetAttribute(CAttribute::NoBlink)))?;
173206 }
174207
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+
175216 let added = self.to - self.from;
176217 if added.contains(Modifier::REVERSED) {
177218 map_error(queue!(w, SetAttribute(CAttribute::Reverse)))?;
@@ -186,16 +227,16 @@ impl ModifierDiff {
186227 map_error(queue!(w, SetAttribute(CAttribute::Underlined)))?;
187228 }
188229 if added.contains(Modifier::UNDERCURLED) {
189− map_error(queue!(w, SetAttribute(CAttribute::Undercurled)))?;
230+ queue_styled_underline(CAttribute::Undercurled, &mut w)?;
190231 }
191232 if added.contains(Modifier::UNDERDOTTED) {
192− map_error(queue!(w, SetAttribute(CAttribute::Underdotted)))?;
233+ queue_styled_underline(CAttribute::Underdotted, &mut w)?;
193234 }
194235 if added.contains(Modifier::UNDERDASHED) {
195− map_error(queue!(w, SetAttribute(CAttribute::Underdashed)))?;
236+ queue_styled_underline(CAttribute::Underdashed, &mut w)?;
196237 }
197238 if added.contains(Modifier::DOUBLE_UNDERLINED) {
198− map_error(queue!(w, SetAttribute(CAttribute::DoubleUnderlined)))?;
239+ queue_styled_underline(CAttribute::DoubleUnderlined, &mut w)?;
199240 }
200241 if added.contains(Modifier::DIM) {
201242 map_error(queue!(w, SetAttribute(CAttribute::Dim)))?;