mirror of https://github.com/helix-editor/helix
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
parent
de72b9c04c
commit
79d3d44c3d
|
@ -176,6 +176,12 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[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"
|
||||
|
@ -504,6 +510,7 @@ dependencies = [
|
|||
"bitflags",
|
||||
"cassowary",
|
||||
"crossterm",
|
||||
"cxterminfo",
|
||||
"helix-core",
|
||||
"helix-view",
|
||||
"serde",
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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)))?;
|
||||
|
|
|
@ -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"] }
|
||||
|
|
Loading…
Reference in New Issue