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).
pull/4061/head
Gokul Soumya 2 years ago committed by Pascal Kuthe
parent de72b9c04c
commit 79d3d44c3d
No known key found for this signature in database
GPG Key ID: D715E8655AE166A6

7
Cargo.lock generated

@ -176,6 +176,12 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "cxterminfo"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da92c5e3aaf2cc1fea346d9b3bac0c59c6ffc1d1d46f18d991d449912a3e6f07"
[[package]] [[package]]
name = "dirs-next" name = "dirs-next"
version = "2.0.0" version = "2.0.0"
@ -504,6 +510,7 @@ dependencies = [
"bitflags", "bitflags",
"cassowary", "cassowary",
"crossterm", "crossterm",
"cxterminfo",
"helix-core", "helix-core",
"helix-view", "helix-view",
"serde", "serde",

@ -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 }
cxterminfo = "0.2"
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" }

@ -11,8 +11,38 @@ use crossterm::{
use helix_view::graphics::{Color, CursorKind, Modifier, Rect}; use helix_view::graphics::{Color, CursorKind, Modifier, Rect};
use std::io::{self, Write}; 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> { pub struct CrosstermBackend<W: Write> {
buffer: W, buffer: W,
capabilities: Capabilities,
} }
impl<W> CrosstermBackend<W> impl<W> CrosstermBackend<W>
@ -20,7 +50,10 @@ where
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(),
}
} }
} }
@ -61,7 +94,7 @@ where
from: modifier, from: modifier,
to: cell.modifier, to: cell.modifier,
}; };
diff.queue(&mut self.buffer)?; diff.queue(&mut self.buffer, self.capabilities)?;
modifier = cell.modifier; modifier = cell.modifier;
} }
if cell.fg != fg { if cell.fg != fg {
@ -141,7 +174,7 @@ struct ModifierDiff {
} }
impl ModifierDiff { impl ModifierDiff {
fn queue<W>(&self, mut w: W) -> io::Result<()> fn queue<W>(&self, mut w: W, caps: Capabilities) -> io::Result<()>
where where
W: io::Write, W: io::Write,
{ {
@ -172,6 +205,14 @@ impl ModifierDiff {
map_error(queue!(w, SetAttribute(CAttribute::NoBlink)))?; 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; let added = self.to - self.from;
if added.contains(Modifier::REVERSED) { if added.contains(Modifier::REVERSED) {
map_error(queue!(w, SetAttribute(CAttribute::Reverse)))?; map_error(queue!(w, SetAttribute(CAttribute::Reverse)))?;
@ -186,16 +227,16 @@ impl ModifierDiff {
map_error(queue!(w, SetAttribute(CAttribute::Underlined)))?; map_error(queue!(w, SetAttribute(CAttribute::Underlined)))?;
} }
if added.contains(Modifier::UNDERCURLED) { if added.contains(Modifier::UNDERCURLED) {
map_error(queue!(w, SetAttribute(CAttribute::Undercurled)))?; queue_styled_underline(CAttribute::Undercurled, &mut w)?;
} }
if added.contains(Modifier::UNDERDOTTED) { if added.contains(Modifier::UNDERDOTTED) {
map_error(queue!(w, SetAttribute(CAttribute::Underdotted)))?; queue_styled_underline(CAttribute::Underdotted, &mut w)?;
} }
if added.contains(Modifier::UNDERDASHED) { 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) { 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) { if added.contains(Modifier::DIM) {
map_error(queue!(w, SetAttribute(CAttribute::Dim)))?; map_error(queue!(w, SetAttribute(CAttribute::Dim)))?;

@ -39,7 +39,10 @@
"diff.delta" = "gold" "diff.delta" = "gold"
"diff.minus" = "red" "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"] } "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…
Cancel
Save