mirror of
https://github.com/helix-editor/helix
synced 2024-11-10 10:34:45 +01:00
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).
This commit is contained in:
parent
de72b9c04c
commit
79d3d44c3d
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -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
Block a user