diff --git a/termwiz/src/caps/mod.rs b/termwiz/src/caps/mod.rs index f0a76b187..9299b2dbc 100644 --- a/termwiz/src/caps/mod.rs +++ b/termwiz/src/caps/mod.rs @@ -107,6 +107,13 @@ builder! { /// Whether mouse support is present and should be used mouse_reporting: Option, + + /// When true, rather than using the terminfo `sgr` or `sgr0` entries, + /// assume that the terminal is ANSI/ECMA-48 compliant for the + /// common SGR attributes of bold, dim, reverse, underline, blink, + /// invisible and reset, and directly emit those sequences. + /// This can improve rendered text compatibility with pagers. + force_terminfo_render_to_use_ansi_sgr: Option, } } @@ -154,6 +161,7 @@ pub struct Capabilities { terminfo_db: Option, bracketed_paste: bool, mouse_reporting: bool, + force_terminfo_render_to_use_ansi_sgr: bool, } impl Capabilities { @@ -282,6 +290,9 @@ impl Capabilities { let bracketed_paste = hints.bracketed_paste.unwrap_or(true); let mouse_reporting = hints.mouse_reporting.unwrap_or(true); + let force_terminfo_render_to_use_ansi_sgr = + hints.force_terminfo_render_to_use_ansi_sgr.unwrap_or(false); + Ok(Self { color_level, sixel, @@ -291,6 +302,7 @@ impl Capabilities { terminfo_db, bracketed_paste, mouse_reporting, + force_terminfo_render_to_use_ansi_sgr, }) } @@ -336,6 +348,12 @@ impl Capabilities { pub fn mouse_reporting(&self) -> bool { self.mouse_reporting } + + /// Whether to emit standard ANSI/ECMA-48 codes, overriding any + /// SGR terminfo capabilities. + pub fn force_terminfo_render_to_use_ansi_sgr(&self) -> bool { + self.force_terminfo_render_to_use_ansi_sgr + } } #[cfg(test)] diff --git a/termwiz/src/render/terminfo.rs b/termwiz/src/render/terminfo.rs index e66a793ac..9d88cf995 100644 --- a/termwiz/src/render/terminfo.rs +++ b/termwiz/src/render/terminfo.rs @@ -50,13 +50,18 @@ impl TerminfoRenderer { #[cfg_attr(feature = "cargo-clippy", allow(clippy::cognitive_complexity))] fn flush_pending_attr(&mut self, out: &mut W) -> Result<()> { macro_rules! attr_on { - ($cap:ident, $sgr:expr) => { - if let Some(attr) = self.get_capability::() { + ($cap:ident, $sgr:expr) => {{ + let cap = if self.caps.force_terminfo_render_to_use_ansi_sgr() { + None + } else { + self.get_capability::() + }; + if let Some(attr) = cap { attr.expand().to(out.by_ref())?; } else { write!(out, "{}", CSI::Sgr($sgr))?; } - }; + }}; ($sgr:expr) => { write!(out, "{}", CSI::Sgr($sgr))?; }; @@ -71,8 +76,13 @@ impl TerminfoRenderer { current_foreground = ColorAttribute::Default; current_background = ColorAttribute::Default; + let sgr = if self.caps.force_terminfo_render_to_use_ansi_sgr() { + None + } else { + self.get_capability::() + }; // The SetAttributes capability can only handle single underline and slow blink. - if let Some(sgr) = self.get_capability::() { + if let Some(sgr) = sgr { sgr.expand() .bold(attr.intensity() == Intensity::Bold) .dim(attr.intensity() == Intensity::Half) @@ -706,10 +716,14 @@ mod test { /// Return Capabilities loaded from the included xterm terminfo data fn xterm_terminfo() -> Capabilities { + xterm_terminfo_with_hints(ProbeHints::default()) + } + + fn xterm_terminfo_with_hints(hints: ProbeHints) -> Capabilities { // Load our own compiled data so that the tests have an // environment that doesn't vary machine by machine. let data = include_bytes!("../../data/xterm-256color"); - Capabilities::new_with_hints(ProbeHints::default().terminfo_db(Some( + Capabilities::new_with_hints(hints.terminfo_db(Some( terminfo::Database::from_buffer(data.as_ref()).unwrap(), ))) .unwrap() @@ -915,6 +929,37 @@ mod test { ); } + #[test] + // Sanity that force_terminfo_render_to_use_ansi_sgr does something. + fn bold_text_force_ansi_sgr() { + let mut out = FakeTerm::new(xterm_terminfo_with_hints( + ProbeHints::default().force_terminfo_render_to_use_ansi_sgr(Some(true)), + )); + out.render(&[ + Change::Text("not ".into()), + AttributeChange::Intensity(Intensity::Bold).into(), + Change::Text("foo".into()), + ]) + .unwrap(); + + let result = out.parse(); + assert_eq!( + result, + vec![ + // Same as bold_text() above, but without the "(B" from srg/sgr0. + Action::Print('n'), + Action::Print('o'), + Action::Print('t'), + Action::Print(' '), + Action::CSI(CSI::Sgr(Sgr::Reset)), + Action::CSI(CSI::Sgr(Sgr::Intensity(Intensity::Bold))), + Action::Print('f'), + Action::Print('o'), + Action::Print('o'), + ], + ); + } + #[test] fn clear_screen() { let mut out = FakeTerm::new_with_size(xterm_terminfo(), 4, 3);