1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-18 19:01:36 +03:00

simplify the terminfo renderer code a little by reducing macro usage

This commit is contained in:
Wez Furlong 2018-07-20 07:18:18 -07:00
parent 4090417b29
commit 5ac675bb5b
3 changed files with 235 additions and 238 deletions

View File

@ -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()?;

View File

@ -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,

View File

@ -14,16 +14,220 @@ use terminfo::{capability as cap, Capability as TermInfoCapability};
pub struct TerminfoRenderer {
caps: Capabilities,
current_attr: CellAttributes,
pending_attr: Option<CellAttributes>,
}
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<T> {
self.caps.terminfo_db().and_then(|db| db.get::<T>())
}
fn attr_apply<F: FnOnce(&mut CellAttributes)>(&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::<cap::$cap_on>() {
attr.expand().to(out)?;
} else {
CSI::Sgr(Sgr::$sgr(value)).encode_escape(&mut out)?;
}
} else {
if let Some(attr) = self.get_capability::<cap::$cap_off>() {
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::<cap::SetAttributes>() {
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::<cap::ExitAttributeMode>() {
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::<cap::EnterBoldMode>() {
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::<cap::EnterDimMode>() {
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::<cap::EnterBlinkMode>() {
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::<cap::EnterReverseMode>() {
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::<cap::SetAForeground>() {
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::<cap::SetABackground>() {
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<CellAttributes, failure::Error> {
let mut current_attr = start_attr.clone();
let mut pending_attr: Option<CellAttributes> = 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::<cap::$cap_on>() {
attr.expand().to(out)?;
} else {
CSI::Sgr(Sgr::$sgr(value)).encode_escape(&mut out)?;
}
} else {
if let Some(attr) = self.get_capability::<cap::$cap_off>() {
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(&current_attr) {
if let Some(sgr) = self.get_capability::<cap::SetAttributes>() {
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::<cap::ExitAttributeMode>()
{
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::<cap::EnterBoldMode>()
{
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::<cap::EnterDimMode>()
{
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::<cap::EnterBlinkMode>()
{
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::<cap::EnterReverseMode>()
{
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::<cap::SetAForeground>()
{
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::<cap::SetABackground>()
{
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(),