termwiz/render/
terminfo.rs

1//! Rendering of Changes using terminfo
2use crate::caps::{Capabilities, ColorLevel};
3use crate::cell::{AttributeChange, Blink, CellAttributes, Intensity, Underline};
4use crate::color::{ColorAttribute, ColorSpec};
5use crate::escape::csi::{Cursor, Edit, EraseInDisplay, EraseInLine, Sgr, CSI};
6use crate::escape::esc::EscCode;
7use crate::escape::osc::{ITermDimension, ITermFileData, ITermProprietary, OperatingSystemCommand};
8use crate::escape::{Esc, OneBased};
9use crate::image::{ImageDataType, TextureCoordinate};
10use crate::render::RenderTty;
11use crate::surface::{Change, CursorShape, CursorVisibility, LineAttribute, Position};
12use crate::Result;
13use std::io::Write;
14use terminfo::{capability as cap, Capability as TermInfoCapability};
15
16pub struct TerminfoRenderer {
17    caps: Capabilities,
18    current_attr: CellAttributes,
19    pending_attr: Option<CellAttributes>,
20    /* TODO: we should record cursor position, shape and color here
21     * so that we can optimize updating them on screen. */
22}
23
24impl TerminfoRenderer {
25    pub fn new(caps: Capabilities) -> Self {
26        Self {
27            caps,
28            current_attr: CellAttributes::default(),
29            pending_attr: None,
30        }
31    }
32
33    fn get_capability<'a, T: TermInfoCapability<'a>>(&'a self) -> Option<T> {
34        self.caps.terminfo_db().and_then(|db| db.get::<T>())
35    }
36
37    fn attr_apply<F: FnOnce(&mut CellAttributes)>(&mut self, func: F) {
38        self.pending_attr = Some(match self.pending_attr.take() {
39            Some(mut attr) => {
40                func(&mut attr);
41                attr
42            }
43            None => {
44                let mut attr = self.current_attr.clone();
45                func(&mut attr);
46                attr
47            }
48        });
49    }
50
51    #[allow(clippy::cognitive_complexity)]
52    fn flush_pending_attr<W: RenderTty + Write>(&mut self, out: &mut W) -> Result<()> {
53        macro_rules! attr_on {
54            ($cap:ident, $sgr:expr) => {{
55                let cap = if self.caps.force_terminfo_render_to_use_ansi_sgr() {
56                    None
57                } else {
58                    self.get_capability::<cap::$cap>()
59                };
60                if let Some(attr) = cap {
61                    attr.expand().to(out.by_ref())?;
62                } else {
63                    write!(out, "{}", CSI::Sgr($sgr))?;
64                }
65            }};
66            ($sgr:expr) => {
67                write!(out, "{}", CSI::Sgr($sgr))?;
68            };
69        }
70
71        if let Some(attr) = self.pending_attr.take() {
72            let mut current_foreground = self.current_attr.foreground();
73            let mut current_background = self.current_attr.background();
74
75            if !attr.attribute_bits_equal(&self.current_attr) {
76                // Updating the attribute bits also resets the colors.
77                current_foreground = ColorAttribute::Default;
78                current_background = ColorAttribute::Default;
79
80                let sgr = if self.caps.force_terminfo_render_to_use_ansi_sgr() {
81                    None
82                } else {
83                    self.get_capability::<cap::SetAttributes>()
84                };
85                // The SetAttributes capability can only handle single underline and slow blink.
86                if let Some(sgr) = sgr {
87                    sgr.expand()
88                        .bold(attr.intensity() == Intensity::Bold)
89                        .dim(attr.intensity() == Intensity::Half)
90                        .underline(attr.underline() == Underline::Single)
91                        .blink(attr.blink() == Blink::Slow)
92                        .reverse(attr.reverse())
93                        .invisible(attr.invisible())
94                        .to(out.by_ref())?;
95                } else {
96                    attr_on!(ExitAttributeMode, Sgr::Reset);
97
98                    match attr.intensity() {
99                        Intensity::Bold => attr_on!(EnterBoldMode, Sgr::Intensity(Intensity::Bold)),
100                        Intensity::Half => attr_on!(EnterDimMode, Sgr::Intensity(Intensity::Half)),
101                        _ => {}
102                    }
103
104                    if attr.underline() == Underline::Single {
105                        attr_on!(Sgr::Underline(Underline::Single));
106                    }
107
108                    if attr.blink() == Blink::Slow {
109                        attr_on!(Sgr::Blink(Blink::Slow));
110                    }
111
112                    if attr.reverse() {
113                        attr_on!(EnterReverseMode, Sgr::Inverse(true));
114                    }
115
116                    if attr.invisible() {
117                        attr_on!(Sgr::Invisible(true));
118                    }
119                }
120
121                if attr.underline() == Underline::Double {
122                    attr_on!(Sgr::Underline(Underline::Double));
123                }
124
125                if attr.blink() == Blink::Rapid {
126                    attr_on!(Sgr::Blink(Blink::Rapid));
127                }
128
129                if attr.italic() {
130                    attr_on!(EnterItalicsMode, Sgr::Italic(true));
131                }
132
133                if attr.strikethrough() {
134                    attr_on!(Sgr::StrikeThrough(true));
135                }
136            }
137
138            let has_true_color = self.caps.color_level() == ColorLevel::TrueColor;
139            // Whether to use terminfo to render 256 colors. If this is too large (ex. 16777216 from xterm-direct),
140            // then setaf expects the index to be true color, in which case we cannot use it to render 256 (or even 16) colors.
141            let terminfo_256_color: i32 = match self.get_capability::<cap::MaxColors>() {
142                Some(cap::MaxColors(n)) => {
143                    if n > 256 {
144                        0
145                    } else {
146                        n
147                    }
148                }
149                None => 0,
150            };
151
152            if attr.foreground() != current_foreground
153                && self.caps.color_level() != ColorLevel::MonoChrome
154            {
155                match (has_true_color, attr.foreground()) {
156                    (true, ColorAttribute::TrueColorWithPaletteFallback(tc, _))
157                    | (true, ColorAttribute::TrueColorWithDefaultFallback(tc)) => {
158                        write!(
159                            out,
160                            "{}",
161                            CSI::Sgr(Sgr::Foreground(ColorSpec::TrueColor(tc)))
162                        )?;
163                    }
164                    (false, ColorAttribute::TrueColorWithDefaultFallback(_))
165                    | (_, ColorAttribute::Default) => {
166                        // Terminfo doesn't define a reset color to default, so
167                        // we use the ANSI code.
168                        write!(out, "{}", CSI::Sgr(Sgr::Foreground(ColorSpec::Default)))?;
169                    }
170                    (false, ColorAttribute::TrueColorWithPaletteFallback(_, idx))
171                    | (_, ColorAttribute::PaletteIndex(idx)) => {
172                        match self.get_capability::<cap::SetAForeground>() {
173                            Some(set) if (idx as i32) < terminfo_256_color => {
174                                set.expand().color(idx).to(out.by_ref())?;
175                            }
176                            _ => {
177                                write!(
178                                    out,
179                                    "{}",
180                                    CSI::Sgr(Sgr::Foreground(ColorSpec::PaletteIndex(idx)))
181                                )?;
182                            }
183                        }
184                    }
185                }
186            }
187
188            if attr.background() != current_background
189                && self.caps.color_level() != ColorLevel::MonoChrome
190            {
191                match (has_true_color, attr.background()) {
192                    (true, ColorAttribute::TrueColorWithPaletteFallback(tc, _))
193                    | (true, ColorAttribute::TrueColorWithDefaultFallback(tc)) => {
194                        write!(
195                            out,
196                            "{}",
197                            CSI::Sgr(Sgr::Background(ColorSpec::TrueColor(tc)))
198                        )?;
199                    }
200                    (false, ColorAttribute::TrueColorWithDefaultFallback(_))
201                    | (_, ColorAttribute::Default) => {
202                        // Terminfo doesn't define a reset color to default, so
203                        // we use the ANSI code.
204                        write!(out, "{}", CSI::Sgr(Sgr::Background(ColorSpec::Default)))?;
205                    }
206                    (false, ColorAttribute::TrueColorWithPaletteFallback(_, idx))
207                    | (_, ColorAttribute::PaletteIndex(idx)) => {
208                        match self.get_capability::<cap::SetABackground>() {
209                            Some(set) if (idx as i32) < terminfo_256_color => {
210                                set.expand().color(idx).to(out.by_ref())?;
211                            }
212                            _ => {
213                                write!(
214                                    out,
215                                    "{}",
216                                    CSI::Sgr(Sgr::Background(ColorSpec::PaletteIndex(idx)))
217                                )?;
218                            }
219                        }
220                    }
221                }
222            }
223
224            if self.caps.hyperlinks() {
225                if let Some(link) = attr.hyperlink() {
226                    let osc = OperatingSystemCommand::SetHyperlink(Some((**link).clone()));
227                    write!(out, "{}", osc)?;
228                } else if self.current_attr.hyperlink().is_some() {
229                    // Close out the old hyperlink
230                    let osc = OperatingSystemCommand::SetHyperlink(None);
231                    write!(out, "{}", osc)?;
232                }
233            }
234
235            self.current_attr = attr;
236        }
237
238        Ok(())
239    }
240
241    fn cursor_up<W: RenderTty + Write>(&mut self, n: u32, out: &mut W) -> Result<()> {
242        if n > 0 {
243            if let Some(attr) = self.get_capability::<cap::ParmUpCursor>() {
244                attr.expand().count(n).to(out.by_ref())?;
245            } else {
246                write!(out, "{}", CSI::Cursor(Cursor::Up(n)))?;
247            }
248        }
249        Ok(())
250    }
251
252    fn cursor_down<W: RenderTty + Write>(&mut self, n: u32, out: &mut W) -> Result<()> {
253        if n > 0 {
254            if let Some(attr) = self.get_capability::<cap::ParmDownCursor>() {
255                attr.expand().count(n).to(out.by_ref())?;
256            } else {
257                write!(out, "{}", CSI::Cursor(Cursor::Down(n)))?;
258            }
259        }
260        Ok(())
261    }
262
263    fn cursor_y_relative<W: RenderTty + Write>(&mut self, y: isize, out: &mut W) -> Result<()> {
264        if y > 0 {
265            self.cursor_down(y as u32, out)
266        } else {
267            self.cursor_up(-y as u32, out)
268        }
269    }
270
271    fn cursor_left<W: RenderTty + Write>(&mut self, n: u32, out: &mut W) -> Result<()> {
272        if n > 0 {
273            if let Some(attr) = self.get_capability::<cap::ParmLeftCursor>() {
274                attr.expand().count(n).to(out.by_ref())?;
275            } else {
276                write!(out, "{}", CSI::Cursor(Cursor::Left(n)))?;
277            }
278        }
279        Ok(())
280    }
281
282    fn cursor_right<W: RenderTty + Write>(&mut self, n: u32, out: &mut W) -> Result<()> {
283        if n > 0 {
284            if let Some(attr) = self.get_capability::<cap::ParmRightCursor>() {
285                attr.expand().count(n).to(out.by_ref())?;
286            } else {
287                write!(out, "{}", CSI::Cursor(Cursor::Right(n)))?;
288            }
289        }
290        Ok(())
291    }
292
293    fn cursor_x_relative<W: RenderTty + Write>(&mut self, x: isize, out: &mut W) -> Result<()> {
294        if x > 0 {
295            self.cursor_right(x as u32, out)
296        } else {
297            self.cursor_left(-x as u32, out)
298        }
299    }
300
301    fn move_cursor_absolute<W: RenderTty + Write>(
302        &mut self,
303        x: u32,
304        y: u32,
305        out: &mut W,
306    ) -> Result<()> {
307        if x == 0 && y == 0 {
308            if let Some(attr) = self.get_capability::<cap::CursorHome>() {
309                attr.expand().to(out.by_ref())?;
310                return Ok(());
311            }
312        }
313
314        if let Some(attr) = self.get_capability::<cap::CursorAddress>() {
315            // terminfo expansion automatically converts coordinates to 1-based,
316            // so we can pass in the 0-based coordinates as-is
317            attr.expand().x(x).y(y).to(out.by_ref())?;
318        } else {
319            // We need to manually convert to 1-based as the CSI representation
320            // requires it and there's no automatic conversion.
321            write!(
322                out,
323                "{}",
324                CSI::Cursor(Cursor::Position {
325                    line: OneBased::from_zero_based(x),
326                    col: OneBased::from_zero_based(y),
327                })
328            )?;
329        }
330        Ok(())
331    }
332
333    #[allow(clippy::cyclomatic_complexity, clippy::cognitive_complexity)]
334    pub fn render_to<W: RenderTty + Write>(
335        &mut self,
336        changes: &[Change],
337        out: &mut W,
338    ) -> Result<()> {
339        macro_rules! record {
340            ($accesor:ident, $value:expr) => {
341                self.attr_apply(|attr| {
342                    attr.$accesor(*$value);
343                });
344            };
345        }
346
347        for change in changes {
348            match change {
349                Change::ClearScreen(color) => {
350                    // ClearScreen implicitly resets all to default
351                    let defaults = CellAttributes::default().set_background(*color).clone();
352                    if self.current_attr != defaults {
353                        self.pending_attr = Some(defaults);
354                        self.flush_pending_attr(out)?;
355                    }
356                    self.pending_attr = None;
357
358                    if self.current_attr.background() == ColorAttribute::Default || self.caps.bce()
359                    {
360                        // The erase operation respects "background color erase",
361                        // or we're clearing to the default background color, so we can
362                        // simply emit a clear screen op.
363                        if let Some(clr) = self.get_capability::<cap::ClearScreen>() {
364                            clr.expand().to(out.by_ref())?;
365                        } else {
366                            self.move_cursor_absolute(0, 0, out)?;
367                            write!(
368                                out,
369                                "{}",
370                                CSI::Edit(Edit::EraseInDisplay(EraseInDisplay::EraseDisplay))
371                            )?;
372                        }
373                    } else {
374                        // We're setting the background to a specific color, so we get to
375                        // paint the whole thing.
376                        self.move_cursor_absolute(0, 0, out)?;
377
378                        let (cols, rows) = out.get_size_in_cells()?;
379                        let num_spaces = cols * rows;
380                        let mut buf = Vec::with_capacity(num_spaces);
381                        buf.resize(num_spaces, b' ');
382                        out.write_all(buf.as_slice())?;
383                    }
384                }
385                Change::ClearToEndOfLine(color) => {
386                    // ClearScreen implicitly resets all to default
387                    let defaults = CellAttributes::default().set_background(*color).clone();
388                    if self.current_attr != defaults {
389                        self.pending_attr = Some(defaults);
390                        self.flush_pending_attr(out)?;
391                    }
392                    self.pending_attr = None;
393
394                    // FIXME: this doesn't behave correctly for terminals without bce.
395                    // If we knew the current cursor position, we would be able to
396                    // emit the correctly colored background for that case.
397                    if let Some(clr) = self.get_capability::<cap::ClrEol>() {
398                        clr.expand().to(out.by_ref())?;
399                    } else {
400                        write!(
401                            out,
402                            "{}",
403                            CSI::Edit(Edit::EraseInLine(EraseInLine::EraseToEndOfLine))
404                        )?;
405                    }
406                }
407                Change::ClearToEndOfScreen(color) => {
408                    // ClearScreen implicitly resets all to default
409                    let defaults = CellAttributes::default().set_background(*color).clone();
410                    if self.current_attr != defaults {
411                        self.pending_attr = Some(defaults);
412                        self.flush_pending_attr(out)?;
413                    }
414                    self.pending_attr = None;
415
416                    // FIXME: this doesn't behave correctly for terminals without bce.
417                    // If we knew the current cursor position, we would be able to
418                    // emit the correctly colored background for that case.
419                    if let Some(clr) = self.get_capability::<cap::ClrEos>() {
420                        clr.expand().to(out.by_ref())?;
421                    } else {
422                        write!(
423                            out,
424                            "{}",
425                            CSI::Edit(Edit::EraseInDisplay(EraseInDisplay::EraseToEndOfDisplay))
426                        )?;
427                    }
428                }
429                Change::Attribute(AttributeChange::Intensity(value)) => {
430                    record!(set_intensity, value);
431                }
432                Change::Attribute(AttributeChange::Italic(value)) => {
433                    record!(set_italic, value);
434                }
435                Change::Attribute(AttributeChange::Reverse(value)) => {
436                    record!(set_reverse, value);
437                }
438                Change::Attribute(AttributeChange::StrikeThrough(value)) => {
439                    record!(set_strikethrough, value);
440                }
441                Change::Attribute(AttributeChange::Blink(value)) => {
442                    record!(set_blink, value);
443                }
444                Change::Attribute(AttributeChange::Invisible(value)) => {
445                    record!(set_invisible, value);
446                }
447                Change::Attribute(AttributeChange::Underline(value)) => {
448                    record!(set_underline, value);
449                }
450                Change::Attribute(AttributeChange::Foreground(col)) => {
451                    self.attr_apply(|attr| {
452                        attr.set_foreground(*col);
453                    });
454                }
455                Change::Attribute(AttributeChange::Background(col)) => {
456                    self.attr_apply(|attr| {
457                        attr.set_background(*col);
458                    });
459                }
460                Change::Attribute(AttributeChange::Hyperlink(link)) => {
461                    self.attr_apply(|attr| {
462                        attr.set_hyperlink(link.clone());
463                    });
464                }
465                Change::AllAttributes(all) => {
466                    self.pending_attr = Some(all.clone());
467                }
468                Change::Text(text) => {
469                    self.flush_pending_attr(out)?;
470                    out.by_ref().write_all(text.as_bytes())?;
471                }
472
473                Change::CursorPosition { x, y } => {
474                    // Note: we use `cursor_up(screen_height)` to move the cursor all the way to
475                    // the top of the screen when we need to absolutely position only y.
476
477                    match (x, y) {
478                        (Position::Relative(0), Position::Relative(0)) => {}
479                        (Position::Absolute(0), Position::Relative(1)) => {
480                            out.by_ref().write_all(b"\r\n")?;
481                        }
482                        (Position::Absolute(x), Position::Relative(y)) => {
483                            self.cursor_y_relative(*y, out)?;
484                            out.by_ref().write_all(b"\r")?;
485                            self.cursor_right(*x as u32, out)?;
486                        }
487
488                        (Position::Relative(x), Position::EndRelative(y)) => {
489                            let (_cols, rows) = out.get_size_in_cells()?;
490                            self.cursor_up(rows as u32, out)?;
491                            self.cursor_down(rows.saturating_sub(y + 1) as u32, out)?;
492                            self.cursor_x_relative(*x, out)?;
493                        }
494                        (Position::Relative(x), Position::Relative(y)) => {
495                            self.cursor_y_relative(*y, out)?;
496                            self.cursor_x_relative(*x, out)?;
497                        }
498                        (Position::EndRelative(x), Position::Relative(y)) => {
499                            self.cursor_y_relative(*y, out)?;
500                            let (cols, _rows) = out.get_size_in_cells()?;
501                            out.by_ref().write_all(b"\r")?;
502                            self.cursor_right(cols.saturating_sub(x + 1) as u32, out)?;
503                        }
504
505                        (Position::Absolute(x), Position::Absolute(y)) => {
506                            self.move_cursor_absolute(*x as u32, *y as u32, out)?;
507                        }
508                        (Position::Absolute(x), Position::EndRelative(y)) => {
509                            let (_cols, rows) = out.get_size_in_cells()?;
510                            self.move_cursor_absolute(
511                                *x as u32,
512                                rows.saturating_sub(y + 1) as u32,
513                                out,
514                            )?;
515                        }
516                        (Position::EndRelative(x), Position::EndRelative(y)) => {
517                            let (cols, rows) = out.get_size_in_cells()?;
518                            self.move_cursor_absolute(
519                                cols.saturating_sub(x + 1) as u32,
520                                rows.saturating_sub(y + 1) as u32,
521                                out,
522                            )?;
523                        }
524
525                        (Position::Relative(x), Position::Absolute(y)) => {
526                            let (_cols, rows) = out.get_size_in_cells()?;
527                            self.cursor_up(rows as u32, out)?;
528                            self.cursor_down(*y as u32, out)?;
529                            self.cursor_x_relative(*x, out)?;
530                        }
531
532                        (Position::EndRelative(x), Position::Absolute(y)) => {
533                            let (cols, _rows) = out.get_size_in_cells()?;
534                            self.move_cursor_absolute(
535                                cols.saturating_sub(x + 1) as u32,
536                                *y as u32,
537                                out,
538                            )?;
539                        }
540                    }
541                }
542
543                Change::CursorColor(_color) => {
544                    // TODO: this isn't spec'd by terminfo, but some terminals
545                    // support it.  Add this to capabilities?
546                }
547                Change::CursorShape(shape) => match shape {
548                    CursorShape::Default => {
549                        if let Some(normal) = self.get_capability::<cap::CursorNormal>() {
550                            normal.expand().to(out.by_ref())?;
551                        }
552                        if let Some(reset) = self.get_capability::<cap::ResetCursorStyle>() {
553                            reset.expand().to(out.by_ref())?;
554                        }
555                    }
556                    _ => {
557                        let param = match shape {
558                            CursorShape::Default => unreachable!(),
559                            CursorShape::BlinkingBlock => 1,
560                            CursorShape::SteadyBlock => 2,
561                            CursorShape::BlinkingUnderline => 3,
562                            CursorShape::SteadyUnderline => 4,
563                            CursorShape::BlinkingBar => 5,
564                            CursorShape::SteadyBar => 6,
565                        };
566                        if let Some(set) = self.get_capability::<cap::SetCursorStyle>() {
567                            set.expand().kind(param).to(out.by_ref())?;
568                        }
569                    }
570                },
571                Change::CursorVisibility(visibility) => match visibility {
572                    CursorVisibility::Visible => {
573                        if let Some(show) = self.get_capability::<cap::CursorNormal>() {
574                            show.expand().to(out.by_ref())?;
575                        }
576                    }
577                    CursorVisibility::Hidden => {
578                        if let Some(hide) = self.get_capability::<cap::CursorInvisible>() {
579                            hide.expand().to(out.by_ref())?;
580                        }
581                    }
582                },
583                Change::Image(image) => {
584                    if self.caps.iterm2_image() {
585                        let data = if image.top_left == TextureCoordinate::new_f32(0.0, 0.0)
586                            && image.bottom_right == TextureCoordinate::new_f32(1.0, 1.0)
587                        {
588                            // The whole image is requested, so we can send the
589                            // original image bytes over
590                            match &*image.image.data() {
591                                ImageDataType::EncodedFile(data) => data.to_vec(),
592                                ImageDataType::EncodedLease(lease) => lease.get_data()?,
593                                ImageDataType::AnimRgba8 { .. } | ImageDataType::Rgba8 { .. } => {
594                                    unimplemented!()
595                                }
596                            }
597                        } else {
598                            // TODO: slice out the requested region of the image,
599                            // and encode as a PNG.
600                            unimplemented!();
601                        };
602
603                        let file = ITermFileData {
604                            name: None,
605                            size: Some(data.len()),
606                            width: ITermDimension::Cells(image.width as i64),
607                            height: ITermDimension::Cells(image.height as i64),
608                            preserve_aspect_ratio: true,
609                            inline: true,
610                            do_not_move_cursor: false,
611                            data,
612                        };
613
614                        let osc = OperatingSystemCommand::ITermProprietary(ITermProprietary::File(
615                            Box::new(file),
616                        ));
617
618                        write!(out, "{}", osc)?;
619
620                    // TODO: } else if self.caps.sixel() {
621                    } else {
622                        // Blank out the cells and move the cursor to the right spot
623                        for y in 0..image.height {
624                            for _ in 0..image.width {
625                                write!(out, " ")?;
626                            }
627
628                            if y != image.height - 1 {
629                                writeln!(out)?;
630                                self.cursor_left(image.width as u32, out)?;
631                            }
632                        }
633                        self.cursor_up(image.height as u32, out)?;
634                    }
635                }
636                Change::ScrollRegionUp {
637                    first_row,
638                    region_size,
639                    scroll_count,
640                } => {
641                    if *region_size > 0 {
642                        if let Some(csr) = self.get_capability::<cap::ChangeScrollRegion>() {
643                            let top = *first_row as u32;
644                            let bottom = (*first_row + *region_size - 1) as u32;
645                            let scroll_count = *scroll_count as u32;
646                            csr.expand().top(top).bottom(bottom).to(out.by_ref())?;
647                            if scroll_count > 0 {
648                                if let Some(scroll) = self.get_capability::<cap::ParmIndex>() {
649                                    scroll.expand().count(scroll_count).to(out.by_ref())?
650                                } else {
651                                    let scroll = self.get_capability::<cap::ScrollForward>();
652                                    let set_position = self.get_capability::<cap::CursorAddress>();
653                                    if let (Some(scroll), Some(set_position)) =
654                                        (scroll, set_position)
655                                    {
656                                        set_position.expand().x(0).y(bottom).to(out.by_ref())?;
657                                        for _ in 0..scroll_count {
658                                            scroll.expand().to(out.by_ref())?
659                                        }
660                                    }
661                                }
662                            }
663                        }
664                    }
665                }
666                Change::ScrollRegionDown {
667                    first_row,
668                    region_size,
669                    scroll_count,
670                } => {
671                    if *region_size > 0 {
672                        if let Some(csr) = self.get_capability::<cap::ChangeScrollRegion>() {
673                            let top = *first_row as u32;
674                            let bottom = (*first_row + *region_size - 1) as u32;
675                            let scroll_count = *scroll_count as u32;
676                            csr.expand().top(top).bottom(bottom).to(out.by_ref())?;
677                            if scroll_count > 0 {
678                                if let Some(scroll) = self.get_capability::<cap::ParmRindex>() {
679                                    scroll.expand().count(scroll_count).to(out.by_ref())?
680                                } else {
681                                    let scroll = self.get_capability::<cap::ScrollReverse>();
682                                    let set_position = self.get_capability::<cap::CursorAddress>();
683                                    if let (Some(scroll), Some(set_position)) =
684                                        (scroll, set_position)
685                                    {
686                                        set_position.expand().x(0).y(top).to(out.by_ref())?;
687                                        for _ in 0..scroll_count {
688                                            scroll.expand().to(out.by_ref())?
689                                        }
690                                    }
691                                }
692                            }
693                        }
694                    }
695                }
696
697                Change::Title(text) => {
698                    let osc = OperatingSystemCommand::SetWindowTitle(text.to_string());
699                    write!(out, "{}", osc)?;
700                }
701
702                Change::LineAttribute(attr) => {
703                    let esc = Esc::Code(match attr {
704                        LineAttribute::DoubleHeightTopHalfLine => {
705                            EscCode::DecDoubleHeightTopHalfLine
706                        }
707                        LineAttribute::DoubleHeightBottomHalfLine => {
708                            EscCode::DecDoubleHeightBottomHalfLine
709                        }
710                        LineAttribute::DoubleWidthLine => EscCode::DecDoubleWidthLine,
711                        LineAttribute::SingleWidthLine => EscCode::DecSingleWidthLine,
712                    });
713                    write!(out, "{esc}")?;
714                }
715            }
716        }
717
718        self.flush_pending_attr(out)?;
719        out.flush()?;
720        Ok(())
721    }
722}
723
724#[cfg(all(test, unix))]
725mod test {
726    use super::*;
727    use crate::bail;
728    use crate::caps::ProbeHints;
729    use crate::color::{AnsiColor, ColorAttribute};
730    use crate::escape::parser::Parser;
731    use crate::escape::{Action, Esc, EscCode};
732    use crate::input::InputEvent;
733    use crate::terminal::unix::{Purge, SetAttributeWhen, UnixTty};
734    use crate::terminal::{cast, ScreenSize, Terminal, TerminalWaker};
735    use libc::winsize;
736    use std::io::{Error as IoError, ErrorKind, Read, Result as IoResult, Write};
737    use std::mem;
738    use std::time::Duration;
739    use terminfo;
740    use termios::Termios;
741
742    /// Return Capabilities loaded from the included xterm terminfo data
743    fn xterm_terminfo() -> Capabilities {
744        xterm_terminfo_with_hints(ProbeHints::default())
745    }
746
747    fn xterm_terminfo_with_hints(hints: ProbeHints) -> Capabilities {
748        // Load our own compiled data so that the tests have an
749        // environment that doesn't vary machine by machine.
750        let data = include_bytes!("../../data/xterm-256color");
751        Capabilities::new_with_hints(hints.terminfo_db(Some(
752            terminfo::Database::from_buffer(data.as_ref()).unwrap(),
753        )))
754        .unwrap()
755    }
756
757    fn no_terminfo_all_enabled() -> Capabilities {
758        Capabilities::new_with_hints(ProbeHints::default().color_level(Some(ColorLevel::TrueColor)))
759            .unwrap()
760    }
761
762    struct FakeTty {
763        buf: Vec<u8>,
764        size: winsize,
765        termios: Termios,
766    }
767
768    impl FakeTty {
769        fn new_with_size(width: usize, height: usize) -> Self {
770            let size = winsize {
771                ws_col: cast(width).unwrap(),
772                ws_row: cast(height).unwrap(),
773                ws_xpixel: 0,
774                ws_ypixel: 0,
775            };
776            let buf = Vec::new();
777            Self {
778                size,
779                buf,
780                termios: unsafe { mem::zeroed() },
781            }
782        }
783    }
784    impl RenderTty for FakeTty {
785        fn get_size_in_cells(&mut self) -> Result<(usize, usize)> {
786            Ok((self.size.ws_col as usize, self.size.ws_row as usize))
787        }
788    }
789
790    impl UnixTty for FakeTty {
791        fn get_size(&mut self) -> Result<winsize> {
792            Ok(self.size.clone())
793        }
794        fn set_size(&mut self, size: winsize) -> Result<()> {
795            self.size = size.clone();
796            Ok(())
797        }
798        fn get_termios(&mut self) -> Result<Termios> {
799            Ok(self.termios.clone())
800        }
801        fn set_termios(&mut self, termios: &Termios, _when: SetAttributeWhen) -> Result<()> {
802            self.termios = termios.clone();
803            Ok(())
804        }
805        /// Waits until all written data has been transmitted.
806        fn drain(&mut self) -> Result<()> {
807            Ok(())
808        }
809        fn purge(&mut self, _purge: Purge) -> Result<()> {
810            Ok(())
811        }
812    }
813
814    impl Read for FakeTty {
815        fn read(&mut self, _buf: &mut [u8]) -> std::result::Result<usize, IoError> {
816            Err(IoError::new(ErrorKind::Other, "not implemented"))
817        }
818    }
819    impl Write for FakeTty {
820        fn write(&mut self, buf: &[u8]) -> IoResult<usize> {
821            self.buf.write(buf)
822        }
823
824        fn flush(&mut self) -> IoResult<()> {
825            self.buf.flush()
826        }
827    }
828
829    struct FakeTerm {
830        write: FakeTty,
831        renderer: TerminfoRenderer,
832    }
833
834    impl FakeTerm {
835        fn new(caps: Capabilities) -> Self {
836            Self::new_with_size(caps, 80, 24)
837        }
838
839        fn new_with_size(caps: Capabilities, width: usize, height: usize) -> Self {
840            let write = FakeTty::new_with_size(width, height);
841            let renderer = TerminfoRenderer::new(caps);
842            Self { write, renderer }
843        }
844
845        fn parse(&self) -> Vec<Action> {
846            let mut p = Parser::new();
847            p.parse_as_vec(&self.write.buf)
848        }
849    }
850
851    impl Terminal for FakeTerm {
852        fn set_raw_mode(&mut self) -> Result<()> {
853            bail!("not implemented");
854        }
855
856        fn set_cooked_mode(&mut self) -> Result<()> {
857            bail!("not implemented");
858        }
859
860        fn enter_alternate_screen(&mut self) -> Result<()> {
861            bail!("not implemented");
862        }
863
864        fn exit_alternate_screen(&mut self) -> Result<()> {
865            bail!("not implemented");
866        }
867
868        fn render(&mut self, changes: &[Change]) -> Result<()> {
869            self.renderer.render_to(changes, &mut self.write)
870        }
871
872        fn get_screen_size(&mut self) -> Result<ScreenSize> {
873            let size = self.write.get_size()?;
874            Ok(ScreenSize {
875                rows: cast(size.ws_row)?,
876                cols: cast(size.ws_col)?,
877                xpixel: cast(size.ws_xpixel)?,
878                ypixel: cast(size.ws_ypixel)?,
879            })
880        }
881
882        fn set_screen_size(&mut self, size: ScreenSize) -> Result<()> {
883            let size = winsize {
884                ws_row: cast(size.rows)?,
885                ws_col: cast(size.cols)?,
886                ws_xpixel: cast(size.xpixel)?,
887                ws_ypixel: cast(size.ypixel)?,
888            };
889
890            self.write.set_size(size)
891        }
892
893        fn flush(&mut self) -> Result<()> {
894            Ok(())
895        }
896
897        fn poll_input(&mut self, _wait: Option<Duration>) -> Result<Option<InputEvent>> {
898            bail!("not implemented");
899        }
900
901        fn waker(&self) -> TerminalWaker {
902            unimplemented!();
903        }
904    }
905
906    #[test]
907    fn empty_render() {
908        let mut out = FakeTerm::new(xterm_terminfo());
909        out.render(&[]).unwrap();
910        assert_eq!("", String::from_utf8(out.write.buf).unwrap());
911        assert_eq!(out.renderer.current_attr, CellAttributes::default());
912    }
913
914    #[test]
915    fn basic_text() {
916        let mut out = FakeTerm::new(xterm_terminfo());
917        out.render(&[Change::Text("foo".into())]).unwrap();
918        assert_eq!("foo", String::from_utf8(out.write.buf).unwrap());
919        assert_eq!(out.renderer.current_attr, CellAttributes::default());
920    }
921
922    #[test]
923    fn bold_text() {
924        let mut out = FakeTerm::new(xterm_terminfo());
925        out.render(&[
926            Change::Text("not ".into()),
927            Change::Attribute(AttributeChange::Intensity(Intensity::Bold)),
928            Change::Text("foo".into()),
929        ])
930        .unwrap();
931
932        let result = out.parse();
933        assert_eq!(
934            result,
935            vec![
936                Action::Print('n'),
937                Action::Print('o'),
938                Action::Print('t'),
939                Action::Print(' '),
940                Action::Esc(Esc::Code(EscCode::AsciiCharacterSetG0)),
941                Action::CSI(CSI::Sgr(Sgr::Reset)),
942                Action::CSI(CSI::Sgr(Sgr::Intensity(Intensity::Bold))),
943                Action::Print('f'),
944                Action::Print('o'),
945                Action::Print('o'),
946            ]
947        );
948
949        assert_eq!(
950            out.renderer.current_attr,
951            CellAttributes::default()
952                .set_intensity(Intensity::Bold)
953                .clone()
954        );
955    }
956
957    #[test]
958    // Sanity that force_terminfo_render_to_use_ansi_sgr does something.
959    fn bold_text_force_ansi_sgr() {
960        let mut out = FakeTerm::new(xterm_terminfo_with_hints(
961            ProbeHints::default().force_terminfo_render_to_use_ansi_sgr(Some(true)),
962        ));
963        out.render(&[
964            Change::Text("not ".into()),
965            AttributeChange::Intensity(Intensity::Bold).into(),
966            Change::Text("foo".into()),
967        ])
968        .unwrap();
969
970        let result = out.parse();
971        assert_eq!(
972            result,
973            vec![
974                // Same as bold_text() above, but without the "(B" from srg/sgr0.
975                Action::Print('n'),
976                Action::Print('o'),
977                Action::Print('t'),
978                Action::Print(' '),
979                Action::CSI(CSI::Sgr(Sgr::Reset)),
980                Action::CSI(CSI::Sgr(Sgr::Intensity(Intensity::Bold))),
981                Action::Print('f'),
982                Action::Print('o'),
983                Action::Print('o'),
984            ],
985        );
986    }
987
988    #[test]
989    fn clear_screen() {
990        let mut out = FakeTerm::new_with_size(xterm_terminfo(), 4, 3);
991        out.render(&[Change::ClearScreen(ColorAttribute::default())])
992            .unwrap();
993
994        let result = out.parse();
995        assert_eq!(
996            result,
997            vec![
998                Action::CSI(CSI::Cursor(Cursor::Position {
999                    line: OneBased::new(1),
1000                    col: OneBased::new(1)
1001                })),
1002                Action::CSI(CSI::Edit(Edit::EraseInDisplay(
1003                    EraseInDisplay::EraseDisplay,
1004                ))),
1005            ]
1006        );
1007
1008        assert_eq!(out.renderer.current_attr, CellAttributes::default());
1009    }
1010
1011    #[test]
1012    fn clear_screen_bce() {
1013        let mut out = FakeTerm::new_with_size(xterm_terminfo(), 4, 3);
1014        out.render(&[Change::ClearScreen(AnsiColor::Maroon.into())])
1015            .unwrap();
1016
1017        let result = out.parse();
1018        assert_eq!(
1019            result,
1020            vec![
1021                Action::CSI(CSI::Sgr(Sgr::Background(AnsiColor::Maroon.into()))),
1022                Action::CSI(CSI::Cursor(Cursor::Position {
1023                    line: OneBased::new(1),
1024                    col: OneBased::new(1)
1025                })),
1026                Action::CSI(CSI::Edit(Edit::EraseInDisplay(
1027                    EraseInDisplay::EraseDisplay,
1028                ))),
1029            ]
1030        );
1031
1032        assert_eq!(
1033            out.renderer.current_attr,
1034            CellAttributes::default()
1035                .set_background(AnsiColor::Maroon)
1036                .clone()
1037        );
1038    }
1039
1040    #[test]
1041    fn clear_screen_no_terminfo() {
1042        let mut out = FakeTerm::new_with_size(no_terminfo_all_enabled(), 4, 3);
1043        out.render(&[Change::ClearScreen(ColorAttribute::default())])
1044            .unwrap();
1045
1046        let result = out.parse();
1047        assert_eq!(
1048            result,
1049            vec![
1050                Action::CSI(CSI::Cursor(Cursor::Position {
1051                    line: OneBased::new(1),
1052                    col: OneBased::new(1)
1053                })),
1054                Action::CSI(CSI::Edit(Edit::EraseInDisplay(
1055                    EraseInDisplay::EraseDisplay,
1056                ))),
1057            ]
1058        );
1059
1060        assert_eq!(out.renderer.current_attr, CellAttributes::default());
1061    }
1062
1063    #[test]
1064    fn clear_screen_bce_no_terminfo() {
1065        let mut out = FakeTerm::new_with_size(no_terminfo_all_enabled(), 4, 3);
1066        out.render(&[Change::ClearScreen(AnsiColor::Maroon.into())])
1067            .unwrap();
1068
1069        let result = out.parse();
1070        assert_eq!(
1071            result,
1072            vec![
1073                Action::CSI(CSI::Sgr(Sgr::Background(AnsiColor::Maroon.into()))),
1074                Action::CSI(CSI::Cursor(Cursor::Position {
1075                    line: OneBased::new(1),
1076                    col: OneBased::new(1)
1077                })),
1078                // bce is not known to be available, so we emit a bunch of spaces.
1079                // TODO: could we use ECMA-48 REP for this?
1080                Action::Print(' '),
1081                Action::Print(' '),
1082                Action::Print(' '),
1083                Action::Print(' '),
1084                Action::Print(' '),
1085                Action::Print(' '),
1086                Action::Print(' '),
1087                Action::Print(' '),
1088                Action::Print(' '),
1089                Action::Print(' '),
1090                Action::Print(' '),
1091                Action::Print(' '),
1092            ]
1093        );
1094
1095        assert_eq!(
1096            out.renderer.current_attr,
1097            CellAttributes::default()
1098                .set_background(AnsiColor::Maroon)
1099                .clone()
1100        );
1101    }
1102
1103    #[test]
1104    fn bold_text_no_terminfo() {
1105        let mut out = FakeTerm::new(no_terminfo_all_enabled());
1106        out.render(&[
1107            Change::Text("not ".into()),
1108            Change::Attribute(AttributeChange::Intensity(Intensity::Bold)),
1109            Change::Text("foo".into()),
1110        ])
1111        .unwrap();
1112
1113        let result = out.parse();
1114        assert_eq!(
1115            result,
1116            vec![
1117                Action::Print('n'),
1118                Action::Print('o'),
1119                Action::Print('t'),
1120                Action::Print(' '),
1121                Action::CSI(CSI::Sgr(Sgr::Reset)),
1122                Action::CSI(CSI::Sgr(Sgr::Intensity(Intensity::Bold))),
1123                Action::Print('f'),
1124                Action::Print('o'),
1125                Action::Print('o'),
1126            ]
1127        );
1128
1129        assert_eq!(
1130            out.renderer.current_attr,
1131            CellAttributes::default()
1132                .set_intensity(Intensity::Bold)
1133                .clone()
1134        );
1135    }
1136
1137    #[test]
1138    fn red_bold_text() {
1139        let mut out = FakeTerm::new(xterm_terminfo());
1140        out.render(&[
1141            Change::Attribute(AttributeChange::Foreground(AnsiColor::Maroon.into())),
1142            Change::Attribute(AttributeChange::Intensity(Intensity::Bold)),
1143            Change::Text("red".into()),
1144            Change::Attribute(AttributeChange::Foreground(AnsiColor::Red.into())),
1145        ])
1146        .unwrap();
1147
1148        let result = out.parse();
1149        assert_eq!(
1150            result,
1151            vec![
1152                Action::Esc(Esc::Code(EscCode::AsciiCharacterSetG0)),
1153                Action::CSI(CSI::Sgr(Sgr::Reset)),
1154                // Note that the render code rearranges (red,bold) to (bold,red)
1155                Action::CSI(CSI::Sgr(Sgr::Intensity(Intensity::Bold))),
1156                Action::CSI(CSI::Sgr(Sgr::Foreground(AnsiColor::Maroon.into()))),
1157                Action::Print('r'),
1158                Action::Print('e'),
1159                Action::Print('d'),
1160                Action::CSI(CSI::Sgr(Sgr::Foreground(AnsiColor::Red.into()))),
1161            ]
1162        );
1163
1164        assert_eq!(
1165            out.renderer.current_attr,
1166            CellAttributes::default()
1167                .set_intensity(Intensity::Bold)
1168                .set_foreground(AnsiColor::Red)
1169                .clone()
1170        );
1171    }
1172
1173    #[test]
1174    fn red_bold_text_no_terminfo() {
1175        let mut out = FakeTerm::new(no_terminfo_all_enabled());
1176        out.render(&[
1177            Change::Attribute(AttributeChange::Foreground(AnsiColor::Maroon.into())),
1178            Change::Attribute(AttributeChange::Intensity(Intensity::Bold)),
1179            Change::Text("red".into()),
1180            Change::Attribute(AttributeChange::Foreground(AnsiColor::Red.into())),
1181        ])
1182        .unwrap();
1183
1184        let result = out.parse();
1185        assert_eq!(
1186            result,
1187            vec![
1188                Action::CSI(CSI::Sgr(Sgr::Reset)),
1189                // Note that the render code rearranges (red,bold) to (bold,red)
1190                Action::CSI(CSI::Sgr(Sgr::Intensity(Intensity::Bold))),
1191                Action::CSI(CSI::Sgr(Sgr::Foreground(AnsiColor::Maroon.into()))),
1192                Action::Print('r'),
1193                Action::Print('e'),
1194                Action::Print('d'),
1195                Action::CSI(CSI::Sgr(Sgr::Foreground(AnsiColor::Red.into()))),
1196            ]
1197        );
1198
1199        assert_eq!(
1200            out.renderer.current_attr,
1201            CellAttributes::default()
1202                .set_intensity(Intensity::Bold)
1203                .set_foreground(AnsiColor::Red)
1204                .clone()
1205        );
1206    }
1207
1208    #[test]
1209    fn color_after_attribute_change() {
1210        let mut out = FakeTerm::new(xterm_terminfo());
1211        out.render(&[
1212            Change::Attribute(AttributeChange::Foreground(AnsiColor::Maroon.into())),
1213            Change::Attribute(AttributeChange::Intensity(Intensity::Bold)),
1214            Change::Text("red".into()),
1215            Change::Attribute(AttributeChange::Intensity(Intensity::Normal)),
1216            Change::Text("2".into()),
1217        ])
1218        .unwrap();
1219
1220        let result = out.parse();
1221        assert_eq!(
1222            result,
1223            vec![
1224                Action::Esc(Esc::Code(EscCode::AsciiCharacterSetG0)),
1225                Action::CSI(CSI::Sgr(Sgr::Reset)),
1226                // Note that the render code rearranges (red,bold) to (bold,red)
1227                Action::CSI(CSI::Sgr(Sgr::Intensity(Intensity::Bold))),
1228                Action::CSI(CSI::Sgr(Sgr::Foreground(AnsiColor::Maroon.into()))),
1229                Action::Print('r'),
1230                Action::Print('e'),
1231                Action::Print('d'),
1232                // Turning off bold is translated into reset and set red again
1233                Action::Esc(Esc::Code(EscCode::AsciiCharacterSetG0)),
1234                Action::CSI(CSI::Sgr(Sgr::Reset)),
1235                Action::CSI(CSI::Sgr(Sgr::Foreground(AnsiColor::Maroon.into()))),
1236                Action::Print('2'),
1237            ]
1238        );
1239
1240        assert_eq!(
1241            out.renderer.current_attr,
1242            CellAttributes::default()
1243                .set_foreground(AnsiColor::Maroon)
1244                .clone()
1245        );
1246    }
1247
1248    #[test]
1249    fn truecolor() {
1250        let mut out = FakeTerm::new(xterm_terminfo());
1251        out.render(&[
1252            Change::Attribute(AttributeChange::Foreground(
1253                ColorSpec::TrueColor((255, 128, 64).into()).into(),
1254            )),
1255            Change::Text("A".into()),
1256        ])
1257        .unwrap();
1258
1259        let result = out.parse();
1260        assert_eq!(
1261            result,
1262            vec![
1263                Action::CSI(CSI::Sgr(Sgr::Foreground(
1264                    ColorSpec::TrueColor((255, 128, 64).into()).into(),
1265                ))),
1266                Action::Print('A'),
1267            ]
1268        );
1269    }
1270
1271    #[test]
1272    fn truecolor_no_terminfo() {
1273        let mut out = FakeTerm::new(no_terminfo_all_enabled());
1274        out.render(&[
1275            Change::Attribute(AttributeChange::Foreground(
1276                ColorSpec::TrueColor((255, 128, 64).into()).into(),
1277            )),
1278            Change::Text("A".into()),
1279        ])
1280        .unwrap();
1281
1282        let result = out.parse();
1283        assert_eq!(
1284            result,
1285            vec![
1286                Action::CSI(CSI::Sgr(Sgr::Foreground(
1287                    ColorSpec::TrueColor((255, 128, 64).into()).into(),
1288                ))),
1289                Action::Print('A'),
1290            ]
1291        );
1292    }
1293}