diff --git a/examples/hello.rs b/examples/hello.rs index 228613418..6b38cab5f 100644 --- a/examples/hello.rs +++ b/examples/hello.rs @@ -12,7 +12,7 @@ use termwiz::terminal::{Terminal, UnixTerminal}; fn main() -> Result<(), Error> { let caps = Capabilities::new_from_env()?; - let renderer = TerminfoRenderer::new(caps); + let mut renderer = TerminfoRenderer::new(caps); let mut terminal = UnixTerminal::new()?; terminal.set_raw_mode()?; diff --git a/src/render/mod.rs b/src/render/mod.rs index bf0c8704a..a0a98e5c5 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -21,7 +21,7 @@ pub trait Renderer { /// value in on subsequent calls to maintain the running view of the /// attributes. fn render_to( - &self, + &mut self, starting_attr: &CellAttributes, changes: &[Change], out: &mut Terminal, diff --git a/src/render/terminfo.rs b/src/render/terminfo.rs index e887fc50b..d1ebf6e16 100644 --- a/src/render/terminfo.rs +++ b/src/render/terminfo.rs @@ -14,16 +14,220 @@ use terminfo::{capability as cap, Capability as TermInfoCapability}; pub struct TerminfoRenderer { caps: Capabilities, + current_attr: CellAttributes, + pending_attr: Option, } impl TerminfoRenderer { pub fn new(caps: Capabilities) -> Self { - Self { caps } + Self { + caps, + current_attr: CellAttributes::default(), + pending_attr: None, + } } fn get_capability<'a, T: TermInfoCapability<'a>>(&'a self) -> Option { self.caps.terminfo_db().and_then(|db| db.get::()) } + + fn attr_apply(&mut self, func: F) { + self.pending_attr = Some(match self.pending_attr.take() { + Some(mut attr) => { + func(&mut attr); + attr + } + None => { + let mut attr = self.current_attr.clone(); + func(&mut attr); + attr + } + }); + } + + fn flush_pending_attr(&mut self, out: &mut Terminal) -> Result<(), failure::Error> { + macro_rules! attr_on_off { + ($cap_on:ident, $cap_off:ident, $attr:expr, $accesor:ident, $sgr:ident) => { + let value = $attr.$accesor(); + if value != self.current_attr.$accesor() { + let mut out = WriteWrapper::new(out); + let on: bool = value.into(); + if on { + if let Some(attr) = self.get_capability::() { + attr.expand().to(out)?; + } else { + CSI::Sgr(Sgr::$sgr(value)).encode_escape(&mut out)?; + } + } else { + if let Some(attr) = self.get_capability::() { + attr.expand().to(out)?; + } else { + CSI::Sgr(Sgr::$sgr(value)).encode_escape(&mut out)?; + } + } + } + }; + } + + if let Some(attr) = self.pending_attr.take() { + if !attr.attribute_bits_equal(&self.current_attr) { + if let Some(sgr) = self.get_capability::() { + sgr.expand() + .bold(attr.intensity() == Intensity::Bold) + .dim(attr.intensity() == Intensity::Half) + .underline(attr.underline() != Underline::None) + .blink(attr.blink() != Blink::None) + .reverse(attr.reverse()) + .invisible(attr.invisible()) + .to(WriteWrapper::new(out))?; + } else { + if let Some(exit) = self.get_capability::() { + exit.expand().to(WriteWrapper::new(out))?; + } else { + CSI::Sgr(Sgr::Reset).encode_escape(&mut WriteWrapper::new(out))?; + } + + if attr.intensity() != self.current_attr.intensity() { + match attr.intensity() { + Intensity::Bold => { + if let Some(bold) = self.get_capability::() { + bold.expand().to(WriteWrapper::new(out))?; + } else { + CSI::Sgr(Sgr::Intensity(attr.intensity())) + .encode_escape(&mut WriteWrapper::new(out))?; + } + } + Intensity::Half => { + if let Some(dim) = self.get_capability::() { + dim.expand().to(WriteWrapper::new(out))?; + } else { + CSI::Sgr(Sgr::Intensity(attr.intensity())) + .encode_escape(&mut WriteWrapper::new(out))?; + } + } + _ => {} + } + } + + attr_on_off!( + EnterUnderlineMode, + ExitUnderlineMode, + attr, + underline, + Underline + ); + + attr_on_off!( + EnterUnderlineMode, + ExitUnderlineMode, + attr, + underline, + Underline + ); + + if attr.blink() != self.current_attr.blink() { + if let Some(attr) = self.get_capability::() { + attr.expand().to(WriteWrapper::new(out))?; + } else { + CSI::Sgr(Sgr::Blink(attr.blink())) + .encode_escape(&mut WriteWrapper::new(out))?; + } + } + + if attr.reverse() != self.current_attr.reverse() { + if let Some(attr) = self.get_capability::() { + attr.expand().to(WriteWrapper::new(out))?; + } else { + CSI::Sgr(Sgr::Inverse(attr.reverse())) + .encode_escape(&mut WriteWrapper::new(out))?; + } + } + + if attr.invisible() != self.current_attr.invisible() { + CSI::Sgr(Sgr::Invisible(attr.invisible())) + .encode_escape(&mut WriteWrapper::new(out))?; + } + } + + attr_on_off!(EnterItalicsMode, ExitItalicsMode, attr, italic, Italic); + + // TODO: add strikethrough to Capabilities + if attr.strikethrough() != self.current_attr.strikethrough() { + CSI::Sgr(Sgr::StrikeThrough(attr.strikethrough())) + .encode_escape(&mut WriteWrapper::new(out))?; + } + } + + let has_true_color = self.caps.color_level() == ColorLevel::TrueColor; + + if attr.foreground != self.current_attr.foreground { + match (has_true_color, attr.foreground) { + (true, ColorAttribute::TrueColorWithPaletteFallback(tc, _)) + | (true, ColorAttribute::TrueColorWithDefaultFallback(tc)) => { + CSI::Sgr(Sgr::Foreground(ColorSpec::TrueColor(tc))) + .encode_escape(&mut WriteWrapper::new(out))?; + } + (false, ColorAttribute::TrueColorWithDefaultFallback(_)) + | (_, ColorAttribute::Default) => { + // Terminfo doesn't define a reset color to default, so + // we use the ANSI code. + CSI::Sgr(Sgr::Foreground(ColorSpec::Default)) + .encode_escape(&mut WriteWrapper::new(out))?; + } + (false, ColorAttribute::TrueColorWithPaletteFallback(_, idx)) + | (_, ColorAttribute::PaletteIndex(idx)) => { + if let Some(set) = self.get_capability::() { + set.expand().color(idx).to(WriteWrapper::new(out))?; + } else { + CSI::Sgr(Sgr::Foreground(ColorSpec::PaletteIndex(idx))) + .encode_escape(&mut WriteWrapper::new(out))?; + } + } + } + } + + if attr.background != self.current_attr.background { + match (has_true_color, attr.background) { + (true, ColorAttribute::TrueColorWithPaletteFallback(tc, _)) + | (true, ColorAttribute::TrueColorWithDefaultFallback(tc)) => { + CSI::Sgr(Sgr::Background(ColorSpec::TrueColor(tc))) + .encode_escape(&mut WriteWrapper::new(out))?; + } + (false, ColorAttribute::TrueColorWithDefaultFallback(_)) + | (_, ColorAttribute::Default) => { + // Terminfo doesn't define a reset color to default, so + // we use the ANSI code. + CSI::Sgr(Sgr::Background(ColorSpec::Default)) + .encode_escape(&mut WriteWrapper::new(out))?; + } + (false, ColorAttribute::TrueColorWithPaletteFallback(_, idx)) + | (_, ColorAttribute::PaletteIndex(idx)) => { + if let Some(set) = self.get_capability::() { + set.expand().color(idx).to(WriteWrapper::new(out))?; + } else { + CSI::Sgr(Sgr::Background(ColorSpec::PaletteIndex(idx))) + .encode_escape(&mut WriteWrapper::new(out))?; + } + } + } + } + + if self.caps.hyperlinks() { + if let Some(link) = attr.hyperlink.as_ref() { + let osc = OperatingSystemCommand::SetHyperlink(Some((**link).clone())); + osc.encode_escape(&mut WriteWrapper::new(out))?; + } else if self.current_attr.hyperlink.is_some() { + // Close out the old hyperlink + let osc = OperatingSystemCommand::SetHyperlink(None); + osc.encode_escape(&mut WriteWrapper::new(out))?; + } + } + + self.current_attr = attr; + } + + Ok(()) + } } // The terminfo crate wants us to move the Write instance in on @@ -51,229 +255,22 @@ impl<'a> Write for WriteWrapper<'a> { impl Renderer for TerminfoRenderer { fn render_to( - &self, + &mut self, start_attr: &CellAttributes, changes: &[Change], out: &mut Terminal, ) -> Result { - let mut current_attr = start_attr.clone(); - let mut pending_attr: Option = None; + self.current_attr = start_attr.clone(); + self.pending_attr = None; - macro_rules! attr_apply { - ($apply:expr) => { - pending_attr = Some(match pending_attr { - Some(mut attr) => { - $apply(&mut attr); - attr - } - None => { - let mut attr = current_attr.clone(); - $apply(&mut attr); - attr - } - }); - }; - } macro_rules! record { ($accesor:ident, $value:expr) => { - attr_apply!(|attr: &mut CellAttributes| { + self.attr_apply(|attr| { attr.$accesor(*$value); }); }; } - macro_rules! attr_on_off { - ($cap_on:ident, $cap_off:ident, $attr:expr, $accesor:ident, $sgr:ident) => { - let value = $attr.$accesor(); - if value != current_attr.$accesor() { - let mut out = WriteWrapper::new(out); - let on: bool = value.into(); - if on { - if let Some(attr) = self.get_capability::() { - attr.expand().to(out)?; - } else { - CSI::Sgr(Sgr::$sgr(value)).encode_escape(&mut out)?; - } - } else { - if let Some(attr) = self.get_capability::() { - attr.expand().to(out)?; - } else { - CSI::Sgr(Sgr::$sgr(value)).encode_escape(&mut out)?; - } - } - } - }; - } - - macro_rules! flush_pending_attr { - () => { - if let Some(attr) = pending_attr.take() { - if !attr.attribute_bits_equal(¤t_attr) { - if let Some(sgr) = self.get_capability::() { - sgr.expand() - .bold(attr.intensity() == Intensity::Bold) - .dim(attr.intensity() == Intensity::Half) - .underline(attr.underline() != Underline::None) - .blink(attr.blink() != Blink::None) - .reverse(attr.reverse()) - .invisible(attr.invisible()) - .to(WriteWrapper::new(out))?; - } else { - if let Some(exit) = self.get_capability::() - { - exit.expand().to(WriteWrapper::new(out))?; - } else { - CSI::Sgr(Sgr::Reset) - .encode_escape(&mut WriteWrapper::new(out))?; - } - - if attr.intensity() != current_attr.intensity() { - match attr.intensity() { - Intensity::Bold => if let Some(bold) = - self.get_capability::() - { - bold.expand().to(WriteWrapper::new(out))?; - } else { - CSI::Sgr(Sgr::Intensity(attr.intensity())) - .encode_escape(&mut WriteWrapper::new(out))?; - }, - Intensity::Half => if let Some(dim) = - self.get_capability::() - { - dim.expand().to(WriteWrapper::new(out))?; - } else { - CSI::Sgr(Sgr::Intensity(attr.intensity())) - .encode_escape(&mut WriteWrapper::new(out))?; - }, - _ => {} - } - } - - attr_on_off!( - EnterUnderlineMode, - ExitUnderlineMode, - attr, - underline, - Underline - ); - - attr_on_off!( - EnterUnderlineMode, - ExitUnderlineMode, - attr, - underline, - Underline - ); - - if attr.blink() != current_attr.blink() { - if let Some(attr) = self.get_capability::() - { - attr.expand().to(WriteWrapper::new(out))?; - } else { - CSI::Sgr(Sgr::Blink(attr.blink())) - .encode_escape(&mut WriteWrapper::new(out))?; - } - } - - if attr.reverse() != current_attr.reverse() { - if let Some(attr) = - self.get_capability::() - { - attr.expand().to(WriteWrapper::new(out))?; - } else { - CSI::Sgr(Sgr::Inverse(attr.reverse())) - .encode_escape(&mut WriteWrapper::new(out))?; - } - } - - if attr.invisible() != current_attr.invisible() { - CSI::Sgr(Sgr::Invisible(attr.invisible())) - .encode_escape(&mut WriteWrapper::new(out))?; - } - } - - attr_on_off!(EnterItalicsMode, ExitItalicsMode, attr, italic, Italic); - - // TODO: add strikethrough to Capabilities - if attr.strikethrough() != current_attr.strikethrough() { - CSI::Sgr(Sgr::StrikeThrough(attr.strikethrough())) - .encode_escape(&mut WriteWrapper::new(out))?; - } - } - - let has_true_color = self.caps.color_level() == ColorLevel::TrueColor; - - if attr.foreground != current_attr.foreground { - match (has_true_color, attr.foreground) { - (true, ColorAttribute::TrueColorWithPaletteFallback(tc, _)) - | (true, ColorAttribute::TrueColorWithDefaultFallback(tc)) => { - CSI::Sgr(Sgr::Foreground(ColorSpec::TrueColor(tc))) - .encode_escape(&mut WriteWrapper::new(out))?; - } - (false, ColorAttribute::TrueColorWithDefaultFallback(_)) - | (_, ColorAttribute::Default) => { - // Terminfo doesn't define a reset color to default, so - // we use the ANSI code. - CSI::Sgr(Sgr::Foreground(ColorSpec::Default)) - .encode_escape(&mut WriteWrapper::new(out))?; - } - (false, ColorAttribute::TrueColorWithPaletteFallback(_, idx)) - | (_, ColorAttribute::PaletteIndex(idx)) => { - if let Some(set) = self.get_capability::() - { - set.expand().color(idx).to(WriteWrapper::new(out))?; - } else { - CSI::Sgr(Sgr::Foreground(ColorSpec::PaletteIndex(idx))) - .encode_escape(&mut WriteWrapper::new(out))?; - } - } - } - } - - if attr.background != current_attr.background { - match (has_true_color, attr.background) { - (true, ColorAttribute::TrueColorWithPaletteFallback(tc, _)) - | (true, ColorAttribute::TrueColorWithDefaultFallback(tc)) => { - CSI::Sgr(Sgr::Background(ColorSpec::TrueColor(tc))) - .encode_escape(&mut WriteWrapper::new(out))?; - } - (false, ColorAttribute::TrueColorWithDefaultFallback(_)) - | (_, ColorAttribute::Default) => { - // Terminfo doesn't define a reset color to default, so - // we use the ANSI code. - CSI::Sgr(Sgr::Background(ColorSpec::Default)) - .encode_escape(&mut WriteWrapper::new(out))?; - } - (false, ColorAttribute::TrueColorWithPaletteFallback(_, idx)) - | (_, ColorAttribute::PaletteIndex(idx)) => { - if let Some(set) = self.get_capability::() - { - set.expand().color(idx).to(WriteWrapper::new(out))?; - } else { - CSI::Sgr(Sgr::Background(ColorSpec::PaletteIndex(idx))) - .encode_escape(&mut WriteWrapper::new(out))?; - } - } - } - } - - if self.caps.hyperlinks() { - if let Some(link) = attr.hyperlink.as_ref() { - let osc = - OperatingSystemCommand::SetHyperlink(Some((**link).clone())); - osc.encode_escape(&mut WriteWrapper::new(out))?; - } else if current_attr.hyperlink.is_some() { - // Close out the old hyperlink - let osc = OperatingSystemCommand::SetHyperlink(None); - osc.encode_escape(&mut WriteWrapper::new(out))?; - } - } - - current_attr = attr; - } - }; - } - for change in changes { match change { Change::ClearScreen(color) => { @@ -281,13 +278,13 @@ impl Renderer for TerminfoRenderer { let defaults = CellAttributes::default() .set_background(color.clone()) .clone(); - if current_attr != defaults { - pending_attr = Some(defaults); - flush_pending_attr!(); + if self.current_attr != defaults { + self.pending_attr = Some(defaults); + self.flush_pending_attr(out)?; } - pending_attr = None; + self.pending_attr = None; - if current_attr.background == ColorAttribute::Default || self.caps.bce() { + if self.current_attr.background == ColorAttribute::Default || self.caps.bce() { // The erase operation respects "background color erase", // or we're clearing to the default background color, so we can // simply emit a clear screen op. @@ -344,19 +341,19 @@ impl Renderer for TerminfoRenderer { record!(set_underline, value); } Change::Attribute(AttributeChange::Foreground(col)) => { - attr_apply!(|attr: &mut CellAttributes| attr.foreground = *col); + self.attr_apply(|attr| attr.foreground = *col); } Change::Attribute(AttributeChange::Background(col)) => { - attr_apply!(|attr: &mut CellAttributes| attr.background = *col); + self.attr_apply(|attr| attr.background = *col); } Change::Attribute(AttributeChange::Hyperlink(link)) => { - attr_apply!(|attr: &mut CellAttributes| attr.hyperlink = link.clone()); + self.attr_apply(|attr| attr.hyperlink = link.clone()); } Change::AllAttributes(all) => { - pending_attr = Some(all.clone()); + self.pending_attr = Some(all.clone()); } Change::Text(text) => { - flush_pending_attr!(); + self.flush_pending_attr(out)?; WriteWrapper::new(out).write_all(text.as_bytes())?; } Change::CursorPosition { @@ -444,8 +441,8 @@ impl Renderer for TerminfoRenderer { } } - flush_pending_attr!(); - Ok(current_attr) + self.flush_pending_attr(out)?; + Ok(self.current_attr.clone()) } } @@ -546,7 +543,7 @@ mod test { #[test] fn empty_render() { let mut out = FakeTerm::new(); - let renderer = TerminfoRenderer::new(xterm_terminfo()); + let mut renderer = TerminfoRenderer::new(xterm_terminfo()); let end_attr = renderer .render_to(&CellAttributes::default(), &[], &mut out) .unwrap(); @@ -557,7 +554,7 @@ mod test { #[test] fn basic_text() { let mut out = FakeTerm::new(); - let renderer = TerminfoRenderer::new(xterm_terminfo()); + let mut renderer = TerminfoRenderer::new(xterm_terminfo()); let end_attr = renderer .render_to( &CellAttributes::default(), @@ -572,7 +569,7 @@ mod test { #[test] fn bold_text() { let mut out = FakeTerm::new(); - let renderer = TerminfoRenderer::new(xterm_terminfo()); + let mut renderer = TerminfoRenderer::new(xterm_terminfo()); let end_attr = renderer .render_to( &CellAttributes::default(), @@ -613,7 +610,7 @@ mod test { #[test] fn clear_screen() { let mut out = FakeTerm::new_with_size(4, 3); - let renderer = TerminfoRenderer::new(xterm_terminfo()); + let mut renderer = TerminfoRenderer::new(xterm_terminfo()); let end_attr = renderer .render_to( &CellAttributes::default(), @@ -639,7 +636,7 @@ mod test { #[test] fn clear_screen_bce() { let mut out = FakeTerm::new_with_size(4, 3); - let renderer = TerminfoRenderer::new(xterm_terminfo()); + let mut renderer = TerminfoRenderer::new(xterm_terminfo()); let end_attr = renderer .render_to( &CellAttributes::default(), @@ -671,7 +668,7 @@ mod test { #[test] fn clear_screen_no_terminfo() { let mut out = FakeTerm::new_with_size(4, 3); - let renderer = TerminfoRenderer::new(no_terminfo_all_enabled()); + let mut renderer = TerminfoRenderer::new(no_terminfo_all_enabled()); let end_attr = renderer .render_to( &CellAttributes::default(), @@ -697,7 +694,7 @@ mod test { #[test] fn clear_screen_bce_no_terminfo() { let mut out = FakeTerm::new_with_size(4, 3); - let renderer = TerminfoRenderer::new(no_terminfo_all_enabled()); + let mut renderer = TerminfoRenderer::new(no_terminfo_all_enabled()); let end_attr = renderer .render_to( &CellAttributes::default(), @@ -740,7 +737,7 @@ mod test { #[test] fn bold_text_no_terminfo() { let mut out = FakeTerm::new(); - let renderer = TerminfoRenderer::new(no_terminfo_all_enabled()); + let mut renderer = TerminfoRenderer::new(no_terminfo_all_enabled()); let end_attr = renderer .render_to( &CellAttributes::default(), @@ -780,7 +777,7 @@ mod test { #[test] fn red_bold_text() { let mut out = FakeTerm::new(); - let renderer = TerminfoRenderer::new(xterm_terminfo()); + let mut renderer = TerminfoRenderer::new(xterm_terminfo()); let end_attr = renderer .render_to( &CellAttributes::default(), @@ -822,7 +819,7 @@ mod test { #[test] fn red_bold_text_no_terminfo() { let mut out = FakeTerm::new(); - let renderer = TerminfoRenderer::new(no_terminfo_all_enabled()); + let mut renderer = TerminfoRenderer::new(no_terminfo_all_enabled()); let end_attr = renderer .render_to( &CellAttributes::default(), @@ -863,7 +860,7 @@ mod test { #[test] fn truecolor() { let mut out = FakeTerm::new(); - let renderer = TerminfoRenderer::new(xterm_terminfo()); + let mut renderer = TerminfoRenderer::new(xterm_terminfo()); renderer .render_to( &CellAttributes::default(), @@ -892,7 +889,7 @@ mod test { #[test] fn truecolor_no_terminfo() { let mut out = FakeTerm::new(); - let renderer = TerminfoRenderer::new(no_terminfo_all_enabled()); + let mut renderer = TerminfoRenderer::new(no_terminfo_all_enabled()); renderer .render_to( &CellAttributes::default(),