@@ -11,8 +11,38 @@ use crossterm::{
11use helix_view::graphics::{Color, CursorKind, Modifier, Rect};
11use helix_view::graphics::{Color, CursorKind, Modifier, Rect};
12use std::io::{self, Write};
12use std::io::{self, Write};
14+fn vte_version() -> Option<usize> {
15+ std::env::var("VTE_VERSION").ok()?.parse().ok()
18+/// Describes terminal capabilities like extended underline, truecolor, etc.
19+#[derive(Copy, Clone, Debug, Default)]
21+ /// Support for undercurled, underdashed, etc.
22+ has_extended_underlines: bool,
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),
14pub struct CrosstermBackend<W: Write> {
43pub struct CrosstermBackend<W: Write> {
15 buffer: W,
44 buffer: W,
45+ capabilities: Capabilities,
18impl<W> CrosstermBackend<W>
48impl<W> CrosstermBackend<W>
@@ -20,7 +50,10 @@ where
22 pub fn new(buffer: W) -> CrosstermBackend<W> {
52 pub fn new(buffer: W) -> CrosstermBackend<W> {
23− CrosstermBackend { buffer }
53+ CrosstermBackend {
55+ capabilities: Capabilities::from_env_or_default(),
@@ -61,7 +94,7 @@ where
61 from: modifier,
94 from: modifier,
62 to: cell.modifier,
95 to: cell.modifier,
64− diff.queue(&mut self.buffer)?;
97+ diff.queue(&mut self.buffer, self.capabilities)?;
65 modifier = cell.modifier;
98 modifier = cell.modifier;
67 if cell.fg != fg {
100 if cell.fg != fg {
@@ -141,7 +174,7 @@ struct ModifierDiff {
143impl ModifierDiff {
176impl ModifierDiff {
144− fn queue<W>(&self, mut w: W) -> io::Result<()>
177+ fn queue<W>(&self, mut w: W, caps: Capabilities) -> io::Result<()>
146 W: io::Write,
179 W: io::Write,
@@ -172,6 +205,14 @@ impl ModifierDiff {
172 map_error(queue!(w, SetAttribute(CAttribute::NoBlink)))?;
205 map_error(queue!(w, SetAttribute(CAttribute::NoBlink)))?;
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,
213+ map_error(queue!(w, SetAttribute(underline)))
175 let added = self.to - self.from;
216 let added = self.to - self.from;
176 if added.contains(Modifier::REVERSED) {
217 if added.contains(Modifier::REVERSED) {
177 map_error(queue!(w, SetAttribute(CAttribute::Reverse)))?;
218 map_error(queue!(w, SetAttribute(CAttribute::Reverse)))?;
@@ -186,16 +227,16 @@ impl ModifierDiff {
186 map_error(queue!(w, SetAttribute(CAttribute::Underlined)))?;
227 map_error(queue!(w, SetAttribute(CAttribute::Underlined)))?;
188 if added.contains(Modifier::UNDERCURLED) {
229 if added.contains(Modifier::UNDERCURLED) {
189− map_error(queue!(w, SetAttribute(CAttribute::Undercurled)))?;
230+ queue_styled_underline(CAttribute::Undercurled, &mut w)?;
191 if added.contains(Modifier::UNDERDOTTED) {
232 if added.contains(Modifier::UNDERDOTTED) {
192− map_error(queue!(w, SetAttribute(CAttribute::Underdotted)))?;
233+ queue_styled_underline(CAttribute::Underdotted, &mut w)?;
194 if added.contains(Modifier::UNDERDASHED) {
235 if added.contains(Modifier::UNDERDASHED) {
195− map_error(queue!(w, SetAttribute(CAttribute::Underdashed)))?;
236+ queue_styled_underline(CAttribute::Underdashed, &mut w)?;
197 if added.contains(Modifier::DOUBLE_UNDERLINED) {
238 if added.contains(Modifier::DOUBLE_UNDERLINED) {
198− map_error(queue!(w, SetAttribute(CAttribute::DoubleUnderlined)))?;
239+ queue_styled_underline(CAttribute::DoubleUnderlined, &mut w)?;
200 if added.contains(Modifier::DIM) {
241 if added.contains(Modifier::DIM) {
201 map_error(queue!(w, SetAttribute(CAttribute::Dim)))?;
242 map_error(queue!(w, SetAttribute(CAttribute::Dim)))?;