Unnamed repository; edit this file 'description' to name the repository.
Detect extended underline support using terminfo
The cxterminfo crate has been used over popular alternatives like `term` since it supports querying for extended capabilities and also for it's small codebase size (which will make it easy to inline it into helix in the future if required).
Gokul Soumya 2022-10-01
parent de72b9c · commit 79d3d44
-rw-r--r--Cargo.lock7
-rw-r--r--helix-tui/Cargo.toml1
-rw-r--r--helix-tui/src/backend/crossterm.rs55
-rw-r--r--runtime/themes/onedark.toml5
4 files changed, 60 insertions, 8 deletions
diff --git a/Cargo.lock b/Cargo.lock
index e5edcaac..f980c417 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -177,6 +177,12 @@ dependencies = [
]
[[package]]
+name = "cxterminfo"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da92c5e3aaf2cc1fea346d9b3bac0c59c6ffc1d1d46f18d991d449912a3e6f07"
+
+[[package]]
name = "dirs-next"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -504,6 +510,7 @@ dependencies = [
"bitflags",
"cassowary",
"crossterm",
+ "cxterminfo",
"helix-core",
"helix-view",
"serde",
diff --git a/helix-tui/Cargo.toml b/helix-tui/Cargo.toml
index b220c64f..1c6a6a8d 100644
--- a/helix-tui/Cargo.toml
+++ b/helix-tui/Cargo.toml
@@ -20,6 +20,7 @@ bitflags = "1.3"
cassowary = "0.3"
unicode-segmentation = "1.10"
crossterm = { version = "0.25", optional = true }
+cxterminfo = "0.2"
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" }
diff --git a/helix-tui/src/backend/crossterm.rs b/helix-tui/src/backend/crossterm.rs
index fe9da919..3a50074e 100644
--- a/helix-tui/src/backend/crossterm.rs
+++ b/helix-tui/src/backend/crossterm.rs
@@ -11,8 +11,38 @@ use crossterm::{
use helix_view::graphics::{Color, CursorKind, Modifier, Rect};
use std::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 cxterminfo::terminfo::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.get_ext_string("Smulx").is_some()
+ || *t.get_ext_bool("Su").unwrap_or(&false)
+ || vte_version() >= Some(5102),
+ },
+ }
+ }
+}
+
pub struct CrosstermBackend<W: Write> {
buffer: W,
+ capabilities: Capabilities,
}
impl<W> CrosstermBackend<W>
@@ -20,7 +50,10 @@ where
W: Write,
{
pub fn new(buffer: W) -> CrosstermBackend<W> {
- CrosstermBackend { buffer }
+ CrosstermBackend {
+ buffer,
+ capabilities: Capabilities::from_env_or_default(),
+ }
}
}
@@ -61,7 +94,7 @@ where
from: modifier,
to: cell.modifier,
};
- diff.queue(&mut self.buffer)?;
+ diff.queue(&mut self.buffer, self.capabilities)?;
modifier = cell.modifier;
}
if cell.fg != fg {
@@ -141,7 +174,7 @@ struct ModifierDiff {
}
impl ModifierDiff {
- fn queue<W>(&self, mut w: W) -> io::Result<()>
+ fn queue<W>(&self, mut w: W, caps: Capabilities) -> io::Result<()>
where
W: io::Write,
{
@@ -172,6 +205,14 @@ impl ModifierDiff {
map_error(queue!(w, SetAttribute(CAttribute::NoBlink)))?;
}
+ let queue_styled_underline = |styled_underline, w: &mut W| -> io::Result<()> {
+ let underline = match caps.has_extended_underlines {
+ true => styled_underline,
+ false => CAttribute::Underlined,
+ };
+ map_error(queue!(w, SetAttribute(underline)))
+ };
+
let added = self.to - self.from;
if added.contains(Modifier::REVERSED) {
map_error(queue!(w, SetAttribute(CAttribute::Reverse)))?;
@@ -186,16 +227,16 @@ impl ModifierDiff {
map_error(queue!(w, SetAttribute(CAttribute::Underlined)))?;
}
if added.contains(Modifier::UNDERCURLED) {
- map_error(queue!(w, SetAttribute(CAttribute::Undercurled)))?;
+ queue_styled_underline(CAttribute::Undercurled, &mut w)?;
}
if added.contains(Modifier::UNDERDOTTED) {
- map_error(queue!(w, SetAttribute(CAttribute::Underdotted)))?;
+ queue_styled_underline(CAttribute::Underdotted, &mut w)?;
}
if added.contains(Modifier::UNDERDASHED) {
- map_error(queue!(w, SetAttribute(CAttribute::Underdashed)))?;
+ queue_styled_underline(CAttribute::Underdashed, &mut w)?;
}
if added.contains(Modifier::DOUBLE_UNDERLINED) {
- map_error(queue!(w, SetAttribute(CAttribute::DoubleUnderlined)))?;
+ queue_styled_underline(CAttribute::DoubleUnderlined, &mut w)?;
}
if added.contains(Modifier::DIM) {
map_error(queue!(w, SetAttribute(CAttribute::Dim)))?;
diff --git a/runtime/themes/onedark.toml b/runtime/themes/onedark.toml
index a4cc12eb..e2bc2c47 100644
--- a/runtime/themes/onedark.toml
+++ b/runtime/themes/onedark.toml
@@ -39,7 +39,10 @@
"diff.delta" = "gold"
"diff.minus" = "red"
-diagnostic = { modifiers = ["undercurled"] }
+"diagnostic.info" = { underline = "blue", modifiers = ["undercurled"] }
+"diagnostic.hint" = { underline = "green", modifiers = ["undercurled"] }
+"diagnostic.warning" = { underline = "yellow", modifiers = ["undercurled"] }
+"diagnostic.error" = { underline = "red", modifiers = ["undercurled"] }
"info" = { fg = "blue", modifiers = ["bold"] }
"hint" = { fg = "green", modifiers = ["bold"] }
"warning" = { fg = "yellow", modifiers = ["bold"] }