@ -7,12 +7,45 @@ use crossterm::{
SetForegroundColor ,
} ,
terminal ::{ self , Clear , ClearType } ,
Command ,
} ;
use helix_view ::graphics ::{ Color , CursorKind , Modifier , Rect } ;
use std ::io ::{ self , Write } ;
use helix_view ::graphics ::{ Color , CursorKind , Modifier , Rect , UnderlineStyle } ;
use std ::{
fmt ,
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 termini ::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 . extended_cap ( "Smulx" ) . is_some ( )
| | t . extended_cap ( "Su" ) . is_some ( )
| | vte_version ( ) > = Some ( 5102 ) ,
} ,
}
}
}
pub struct CrosstermBackend < W : Write > {
buffer : W ,
capabilities : Capabilities ,
}
impl < W > CrosstermBackend < W >
@ -20,7 +53,10 @@ where
W : Write ,
{
pub fn new ( buffer : W ) -> CrosstermBackend < W > {
CrosstermBackend { buffer }
CrosstermBackend {
buffer ,
capabilities : Capabilities ::from_env_or_default ( ) ,
}
}
}
@ -47,6 +83,8 @@ where
{
let mut fg = Color ::Reset ;
let mut bg = Color ::Reset ;
let mut underline_color = Color ::Reset ;
let mut underline_style = UnderlineStyle ::Reset ;
let mut modifier = Modifier ::empty ( ) ;
let mut last_pos : Option < ( u16 , u16 ) > = None ;
for ( x , y , cell ) in content {
@ -74,11 +112,32 @@ where
bg = cell . bg ;
}
let mut new_underline_style = cell . underline_style ;
if self . capabilities . has_extended_underlines {
if cell . underline_color ! = underline_color {
let color = CColor ::from ( cell . underline_color ) ;
map_error ( queue ! ( self . buffer , SetUnderlineColor ( color ) ) ) ? ;
underline_color = cell . underline_color ;
}
} else {
match new_underline_style {
UnderlineStyle ::Reset | UnderlineStyle ::Line = > ( ) ,
_ = > new_underline_style = UnderlineStyle ::Line ,
}
}
if new_underline_style ! = underline_style {
let attr = CAttribute ::from ( new_underline_style ) ;
map_error ( queue ! ( self . buffer , SetAttribute ( attr ) ) ) ? ;
underline_style = new_underline_style ;
}
map_error ( queue ! ( self . buffer , Print ( & cell . symbol ) ) ) ? ;
}
map_error ( queue ! (
self . buffer ,
SetUnderlineColor ( CColor ::Reset ) ,
SetForegroundColor ( CColor ::Reset ) ,
SetBackgroundColor ( CColor ::Reset ) ,
SetAttribute ( CAttribute ::Reset )
@ -153,9 +212,6 @@ impl ModifierDiff {
if removed . contains ( Modifier ::ITALIC ) {
map_error ( queue ! ( w , SetAttribute ( CAttribute ::NoItalic ) ) ) ? ;
}
if removed . contains ( Modifier ::UNDERLINED ) {
map_error ( queue ! ( w , SetAttribute ( CAttribute ::NoUnderline ) ) ) ? ;
}
if removed . contains ( Modifier ::DIM ) {
map_error ( queue ! ( w , SetAttribute ( CAttribute ::NormalIntensity ) ) ) ? ;
}
@ -176,9 +232,6 @@ impl ModifierDiff {
if added . contains ( Modifier ::ITALIC ) {
map_error ( queue ! ( w , SetAttribute ( CAttribute ::Italic ) ) ) ? ;
}
if added . contains ( Modifier ::UNDERLINED ) {
map_error ( queue ! ( w , SetAttribute ( CAttribute ::Underlined ) ) ) ? ;
}
if added . contains ( Modifier ::DIM ) {
map_error ( queue ! ( w , SetAttribute ( CAttribute ::Dim ) ) ) ? ;
}
@ -195,3 +248,58 @@ impl ModifierDiff {
Ok ( ( ) )
}
}
/// Crossterm uses semicolon as a seperator for colors
/// this is actually not spec compliant (altough commonly supported)
/// However the correct approach is to use colons as a seperator.
/// This usually doesn't make a difference for emulators that do support colored underlines.
/// However terminals that do not support colored underlines will ignore underlines colors with colons
/// while escape sequences with semicolons are always processed which leads to weird visual artifacts.
/// See [this nvim issue](https://github.com/neovim/neovim/issues/9270) for details
#[ derive(Debug, Clone, Copy, PartialEq, Eq) ]
pub struct SetUnderlineColor ( pub CColor ) ;
impl Command for SetUnderlineColor {
fn write_ansi ( & self , f : & mut impl fmt ::Write ) -> fmt ::Result {
let color = self . 0 ;
if color = = CColor ::Reset {
write! ( f , "\x1b[59m" ) ? ;
return Ok ( ( ) ) ;
}
f . write_str ( "\x1b[58:" ) ? ;
let res = match color {
CColor ::Black = > f . write_str ( "5:0" ) ,
CColor ::DarkGrey = > f . write_str ( "5:8" ) ,
CColor ::Red = > f . write_str ( "5:9" ) ,
CColor ::DarkRed = > f . write_str ( "5:1" ) ,
CColor ::Green = > f . write_str ( "5:10" ) ,
CColor ::DarkGreen = > f . write_str ( "5:2" ) ,
CColor ::Yellow = > f . write_str ( "5:11" ) ,
CColor ::DarkYellow = > f . write_str ( "5:3" ) ,
CColor ::Blue = > f . write_str ( "5:12" ) ,
CColor ::DarkBlue = > f . write_str ( "5:4" ) ,
CColor ::Magenta = > f . write_str ( "5:13" ) ,
CColor ::DarkMagenta = > f . write_str ( "5:5" ) ,
CColor ::Cyan = > f . write_str ( "5:14" ) ,
CColor ::DarkCyan = > f . write_str ( "5:6" ) ,
CColor ::White = > f . write_str ( "5:15" ) ,
CColor ::Grey = > f . write_str ( "5:7" ) ,
CColor ::Rgb { r , g , b } = > write! ( f , "2::{}:{}:{}" , r , g , b ) ,
CColor ::AnsiValue ( val ) = > write! ( f , "5:{}" , val ) ,
_ = > Ok ( ( ) ) ,
} ;
res ? ;
write! ( f , "m" ) ? ;
Ok ( ( ) )
}
#[ cfg(windows) ]
fn execute_winapi ( & self ) -> crossterm ::Result < ( ) > {
Err ( std ::io ::Error ::new (
std ::io ::ErrorKind ::Other ,
"SetUnderlineColor not supported by winapi." ,
) )
}
}