1#![cfg_attr(docsrs, feature(doc_cfg))]
3#![doc(
4 html_logo_url = "https://raw.githubusercontent.com/ratatui/ratatui/main/assets/logo.png",
5 html_favicon_url = "https://raw.githubusercontent.com/ratatui/ratatui/main/assets/favicon.ico"
6)]
7#![warn(missing_docs)]
8#![cfg_attr(feature = "document-features", doc = "\n## Features")]
36#![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
37
38use std::fmt;
39use std::io::{self, Write};
40
41use ratatui_core::backend::{Backend, ClearType, WindowSize};
42use ratatui_core::buffer::Cell;
43use ratatui_core::layout::{Position, Size};
44use ratatui_core::style::{Color, Modifier, Style};
45pub use termion;
46use termion::color::Color as _;
47use termion::{color as tcolor, style as tstyle};
48
49#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
95pub struct TermionBackend<W>
96where
97 W: Write,
98{
99 writer: W,
100}
101
102impl<W> TermionBackend<W>
103where
104 W: Write,
105{
106 pub const fn new(writer: W) -> Self {
123 Self { writer }
124 }
125
126 #[instability::unstable(
128 feature = "backend-writer",
129 issue = "https://github.com/ratatui/ratatui/pull/991"
130 )]
131 pub const fn writer(&self) -> &W {
132 &self.writer
133 }
134
135 #[instability::unstable(
139 feature = "backend-writer",
140 issue = "https://github.com/ratatui/ratatui/pull/991"
141 )]
142 pub const fn writer_mut(&mut self) -> &mut W {
143 &mut self.writer
144 }
145}
146
147impl<W> Write for TermionBackend<W>
148where
149 W: Write,
150{
151 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
152 self.writer.write(buf)
153 }
154
155 fn flush(&mut self) -> io::Result<()> {
156 self.writer.flush()
157 }
158}
159
160impl<W> Backend for TermionBackend<W>
161where
162 W: Write,
163{
164 type Error = io::Error;
165
166 fn clear(&mut self) -> io::Result<()> {
167 self.clear_region(ClearType::All)
168 }
169
170 fn clear_region(&mut self, clear_type: ClearType) -> io::Result<()> {
171 match clear_type {
172 ClearType::All => write!(self.writer, "{}", termion::clear::All)?,
173 ClearType::AfterCursor => write!(self.writer, "{}", termion::clear::AfterCursor)?,
174 ClearType::BeforeCursor => write!(self.writer, "{}", termion::clear::BeforeCursor)?,
175 ClearType::CurrentLine => write!(self.writer, "{}", termion::clear::CurrentLine)?,
176 ClearType::UntilNewLine => write!(self.writer, "{}", termion::clear::UntilNewline)?,
177 }
178 self.writer.flush()
179 }
180
181 fn append_lines(&mut self, n: u16) -> io::Result<()> {
182 for _ in 0..n {
183 writeln!(self.writer)?;
184 }
185 self.writer.flush()
186 }
187
188 fn hide_cursor(&mut self) -> io::Result<()> {
189 write!(self.writer, "{}", termion::cursor::Hide)?;
190 self.writer.flush()
191 }
192
193 fn show_cursor(&mut self) -> io::Result<()> {
194 write!(self.writer, "{}", termion::cursor::Show)?;
195 self.writer.flush()
196 }
197
198 fn get_cursor_position(&mut self) -> io::Result<Position> {
199 termion::cursor::DetectCursorPos::cursor_pos(&mut self.writer)
200 .map(|(x, y)| Position { x: x - 1, y: y - 1 })
201 }
202
203 fn set_cursor_position<P: Into<Position>>(&mut self, position: P) -> io::Result<()> {
204 let Position { x, y } = position.into();
205 write!(self.writer, "{}", termion::cursor::Goto(x + 1, y + 1))?;
206 self.writer.flush()
207 }
208
209 fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
210 where
211 I: Iterator<Item = (u16, u16, &'a Cell)>,
212 {
213 use std::fmt::Write;
214
215 let mut string = String::with_capacity(content.size_hint().0 * 3);
216 let mut fg = Color::Reset;
217 let mut bg = Color::Reset;
218 let mut modifier = Modifier::empty();
219 let mut last_pos: Option<Position> = None;
220 for (x, y, cell) in content {
221 if !matches!(last_pos, Some(p) if x == p.x + 1 && y == p.y) {
223 write!(string, "{}", termion::cursor::Goto(x + 1, y + 1)).unwrap();
224 }
225 last_pos = Some(Position { x, y });
226 if cell.modifier != modifier {
227 write!(
228 string,
229 "{}",
230 ModifierDiff {
231 from: modifier,
232 to: cell.modifier
233 }
234 )
235 .unwrap();
236 modifier = cell.modifier;
237 }
238 if cell.fg != fg {
239 write!(string, "{}", Fg(cell.fg)).unwrap();
240 fg = cell.fg;
241 }
242 if cell.bg != bg {
243 write!(string, "{}", Bg(cell.bg)).unwrap();
244 bg = cell.bg;
245 }
246 string.push_str(cell.symbol());
247 }
248 write!(
249 self.writer,
250 "{string}{}{}{}",
251 Fg(Color::Reset),
252 Bg(Color::Reset),
253 termion::style::Reset,
254 )
255 }
256
257 fn size(&self) -> io::Result<Size> {
258 let terminal = termion::terminal_size()?;
259 Ok(Size::new(terminal.0, terminal.1))
260 }
261
262 fn window_size(&mut self) -> io::Result<WindowSize> {
263 Ok(WindowSize {
264 columns_rows: termion::terminal_size()?.into(),
265 pixels: termion::terminal_size_pixels()?.into(),
266 })
267 }
268
269 fn flush(&mut self) -> io::Result<()> {
270 self.writer.flush()
271 }
272
273 #[cfg(feature = "scrolling-regions")]
274 fn scroll_region_up(&mut self, region: std::ops::Range<u16>, amount: u16) -> io::Result<()> {
275 write!(
276 self.writer,
277 "{}{}{}",
278 SetRegion(region.start.saturating_add(1), region.end),
279 termion::scroll::Up(amount),
280 ResetRegion,
281 )?;
282 self.writer.flush()
283 }
284
285 #[cfg(feature = "scrolling-regions")]
286 fn scroll_region_down(&mut self, region: std::ops::Range<u16>, amount: u16) -> io::Result<()> {
287 write!(
288 self.writer,
289 "{}{}{}",
290 SetRegion(region.start.saturating_add(1), region.end),
291 termion::scroll::Down(amount),
292 ResetRegion,
293 )?;
294 self.writer.flush()
295 }
296}
297struct Fg(Color);
298
299struct Bg(Color);
300
301struct ModifierDiff {
305 from: Modifier,
306 to: Modifier,
307}
308
309impl fmt::Display for Fg {
310 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
311 match self.0 {
312 Color::Reset => termion::color::Reset.write_fg(f),
313 Color::Black => termion::color::Black.write_fg(f),
314 Color::Red => termion::color::Red.write_fg(f),
315 Color::Green => termion::color::Green.write_fg(f),
316 Color::Yellow => termion::color::Yellow.write_fg(f),
317 Color::Blue => termion::color::Blue.write_fg(f),
318 Color::Magenta => termion::color::Magenta.write_fg(f),
319 Color::Cyan => termion::color::Cyan.write_fg(f),
320 Color::Gray => termion::color::White.write_fg(f),
321 Color::DarkGray => termion::color::LightBlack.write_fg(f),
322 Color::LightRed => termion::color::LightRed.write_fg(f),
323 Color::LightGreen => termion::color::LightGreen.write_fg(f),
324 Color::LightBlue => termion::color::LightBlue.write_fg(f),
325 Color::LightYellow => termion::color::LightYellow.write_fg(f),
326 Color::LightMagenta => termion::color::LightMagenta.write_fg(f),
327 Color::LightCyan => termion::color::LightCyan.write_fg(f),
328 Color::White => termion::color::LightWhite.write_fg(f),
329 Color::Indexed(i) => termion::color::AnsiValue(i).write_fg(f),
330 Color::Rgb(r, g, b) => termion::color::Rgb(r, g, b).write_fg(f),
331 }
332 }
333}
334impl fmt::Display for Bg {
335 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
336 match self.0 {
337 Color::Reset => termion::color::Reset.write_bg(f),
338 Color::Black => termion::color::Black.write_bg(f),
339 Color::Red => termion::color::Red.write_bg(f),
340 Color::Green => termion::color::Green.write_bg(f),
341 Color::Yellow => termion::color::Yellow.write_bg(f),
342 Color::Blue => termion::color::Blue.write_bg(f),
343 Color::Magenta => termion::color::Magenta.write_bg(f),
344 Color::Cyan => termion::color::Cyan.write_bg(f),
345 Color::Gray => termion::color::White.write_bg(f),
346 Color::DarkGray => termion::color::LightBlack.write_bg(f),
347 Color::LightRed => termion::color::LightRed.write_bg(f),
348 Color::LightGreen => termion::color::LightGreen.write_bg(f),
349 Color::LightBlue => termion::color::LightBlue.write_bg(f),
350 Color::LightYellow => termion::color::LightYellow.write_bg(f),
351 Color::LightMagenta => termion::color::LightMagenta.write_bg(f),
352 Color::LightCyan => termion::color::LightCyan.write_bg(f),
353 Color::White => termion::color::LightWhite.write_bg(f),
354 Color::Indexed(i) => termion::color::AnsiValue(i).write_bg(f),
355 Color::Rgb(r, g, b) => termion::color::Rgb(r, g, b).write_bg(f),
356 }
357 }
358}
359
360pub trait FromTermion<T> {
365 fn from_termion(termion: T) -> Self;
367}
368
369pub trait IntoTermion<T> {
374 fn into_termion(self) -> T;
376}
377
378macro_rules! from_termion_for_color {
379 ($termion_color:ident, $color:ident) => {
380 impl FromTermion<tcolor::$termion_color> for Color {
381 fn from_termion(_: tcolor::$termion_color) -> Self {
382 Color::$color
383 }
384 }
385
386 impl FromTermion<tcolor::Bg<tcolor::$termion_color>> for Style {
387 fn from_termion(_: tcolor::Bg<tcolor::$termion_color>) -> Self {
388 Style::default().bg(Color::$color)
389 }
390 }
391
392 impl FromTermion<tcolor::Fg<tcolor::$termion_color>> for Style {
393 fn from_termion(_: tcolor::Fg<tcolor::$termion_color>) -> Self {
394 Style::default().fg(Color::$color)
395 }
396 }
397 };
398}
399
400from_termion_for_color!(Reset, Reset);
401from_termion_for_color!(Black, Black);
402from_termion_for_color!(Red, Red);
403from_termion_for_color!(Green, Green);
404from_termion_for_color!(Yellow, Yellow);
405from_termion_for_color!(Blue, Blue);
406from_termion_for_color!(Magenta, Magenta);
407from_termion_for_color!(Cyan, Cyan);
408from_termion_for_color!(White, Gray);
409from_termion_for_color!(LightBlack, DarkGray);
410from_termion_for_color!(LightRed, LightRed);
411from_termion_for_color!(LightGreen, LightGreen);
412from_termion_for_color!(LightBlue, LightBlue);
413from_termion_for_color!(LightYellow, LightYellow);
414from_termion_for_color!(LightMagenta, LightMagenta);
415from_termion_for_color!(LightCyan, LightCyan);
416from_termion_for_color!(LightWhite, White);
417
418impl FromTermion<tcolor::AnsiValue> for Color {
419 fn from_termion(value: tcolor::AnsiValue) -> Self {
420 Self::Indexed(value.0)
421 }
422}
423
424impl FromTermion<tcolor::Bg<tcolor::AnsiValue>> for Style {
425 fn from_termion(value: tcolor::Bg<tcolor::AnsiValue>) -> Self {
426 Self::default().bg(Color::Indexed(value.0.0))
427 }
428}
429
430impl FromTermion<tcolor::Fg<tcolor::AnsiValue>> for Style {
431 fn from_termion(value: tcolor::Fg<tcolor::AnsiValue>) -> Self {
432 Self::default().fg(Color::Indexed(value.0.0))
433 }
434}
435
436impl FromTermion<tcolor::Rgb> for Color {
437 fn from_termion(value: tcolor::Rgb) -> Self {
438 Self::Rgb(value.0, value.1, value.2)
439 }
440}
441
442impl FromTermion<tcolor::Bg<tcolor::Rgb>> for Style {
443 fn from_termion(value: tcolor::Bg<tcolor::Rgb>) -> Self {
444 Self::default().bg(Color::Rgb(value.0.0, value.0.1, value.0.2))
445 }
446}
447
448impl FromTermion<tcolor::Fg<tcolor::Rgb>> for Style {
449 fn from_termion(value: tcolor::Fg<tcolor::Rgb>) -> Self {
450 Self::default().fg(Color::Rgb(value.0.0, value.0.1, value.0.2))
451 }
452}
453
454impl fmt::Display for ModifierDiff {
455 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
456 let remove = self.from - self.to;
457 if remove.contains(Modifier::REVERSED) {
458 write!(f, "{}", termion::style::NoInvert)?;
459 }
460 if remove.contains(Modifier::BOLD) {
461 write!(f, "{}", termion::style::NoFaint)?;
465
466 if self.to.contains(Modifier::DIM) {
467 write!(f, "{}", termion::style::Faint)?;
468 }
469 }
470 if remove.contains(Modifier::ITALIC) {
471 write!(f, "{}", termion::style::NoItalic)?;
472 }
473 if remove.contains(Modifier::UNDERLINED) {
474 write!(f, "{}", termion::style::NoUnderline)?;
475 }
476 if remove.contains(Modifier::DIM) {
477 write!(f, "{}", termion::style::NoFaint)?;
478
479 if self.to.contains(Modifier::BOLD) {
482 write!(f, "{}", termion::style::Bold)?;
483 }
484 }
485 if remove.contains(Modifier::CROSSED_OUT) {
486 write!(f, "{}", termion::style::NoCrossedOut)?;
487 }
488 if remove.contains(Modifier::SLOW_BLINK) || remove.contains(Modifier::RAPID_BLINK) {
489 write!(f, "{}", termion::style::NoBlink)?;
490 }
491
492 let add = self.to - self.from;
493 if add.contains(Modifier::REVERSED) {
494 write!(f, "{}", termion::style::Invert)?;
495 }
496 if add.contains(Modifier::BOLD) {
497 write!(f, "{}", termion::style::Bold)?;
498 }
499 if add.contains(Modifier::ITALIC) {
500 write!(f, "{}", termion::style::Italic)?;
501 }
502 if add.contains(Modifier::UNDERLINED) {
503 write!(f, "{}", termion::style::Underline)?;
504 }
505 if add.contains(Modifier::DIM) {
506 write!(f, "{}", termion::style::Faint)?;
507 }
508 if add.contains(Modifier::CROSSED_OUT) {
509 write!(f, "{}", termion::style::CrossedOut)?;
510 }
511 if add.contains(Modifier::SLOW_BLINK) || add.contains(Modifier::RAPID_BLINK) {
512 write!(f, "{}", termion::style::Blink)?;
513 }
514
515 Ok(())
516 }
517}
518
519macro_rules! from_termion_for_modifier {
520 ($termion_modifier:ident, $modifier:ident) => {
521 impl FromTermion<tstyle::$termion_modifier> for Modifier {
522 fn from_termion(_: tstyle::$termion_modifier) -> Self {
523 Modifier::$modifier
524 }
525 }
526 };
527}
528
529from_termion_for_modifier!(Invert, REVERSED);
530from_termion_for_modifier!(Bold, BOLD);
531from_termion_for_modifier!(Italic, ITALIC);
532from_termion_for_modifier!(Underline, UNDERLINED);
533from_termion_for_modifier!(Faint, DIM);
534from_termion_for_modifier!(CrossedOut, CROSSED_OUT);
535from_termion_for_modifier!(Blink, SLOW_BLINK);
536
537impl FromTermion<termion::style::Reset> for Modifier {
538 fn from_termion(_: termion::style::Reset) -> Self {
539 Self::empty()
540 }
541}
542
543#[derive(Copy, Clone, PartialEq, Eq)]
545pub struct SetRegion(pub u16, pub u16);
546
547impl fmt::Display for SetRegion {
548 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
549 write!(f, "\x1B[{};{}r", self.0, self.1)
550 }
551}
552
553#[derive(Copy, Clone, PartialEq, Eq)]
555pub struct ResetRegion;
556
557impl fmt::Display for ResetRegion {
558 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
559 write!(f, "\x1B[r")
560 }
561}
562
563#[cfg(test)]
564mod tests {
565 use super::*;
566
567 #[test]
568 fn from_termion_color() {
569 assert_eq!(Color::from_termion(tcolor::Reset), Color::Reset);
570 assert_eq!(Color::from_termion(tcolor::Black), Color::Black);
571 assert_eq!(Color::from_termion(tcolor::Red), Color::Red);
572 assert_eq!(Color::from_termion(tcolor::Green), Color::Green);
573 assert_eq!(Color::from_termion(tcolor::Yellow), Color::Yellow);
574 assert_eq!(Color::from_termion(tcolor::Blue), Color::Blue);
575 assert_eq!(Color::from_termion(tcolor::Magenta), Color::Magenta);
576 assert_eq!(Color::from_termion(tcolor::Cyan), Color::Cyan);
577 assert_eq!(Color::from_termion(tcolor::White), Color::Gray);
578 assert_eq!(Color::from_termion(tcolor::LightBlack), Color::DarkGray);
579 assert_eq!(Color::from_termion(tcolor::LightRed), Color::LightRed);
580 assert_eq!(Color::from_termion(tcolor::LightGreen), Color::LightGreen);
581 assert_eq!(Color::from_termion(tcolor::LightBlue), Color::LightBlue);
582 assert_eq!(Color::from_termion(tcolor::LightYellow), Color::LightYellow);
583 assert_eq!(
584 Color::from_termion(tcolor::LightMagenta),
585 Color::LightMagenta
586 );
587 assert_eq!(Color::from_termion(tcolor::LightCyan), Color::LightCyan);
588 assert_eq!(Color::from_termion(tcolor::LightWhite), Color::White);
589 assert_eq!(
590 Color::from_termion(tcolor::AnsiValue(31)),
591 Color::Indexed(31)
592 );
593 assert_eq!(
594 Color::from_termion(tcolor::Rgb(1, 2, 3)),
595 Color::Rgb(1, 2, 3)
596 );
597 }
598
599 #[test]
600 fn from_termion_bg() {
601 use tc::Bg;
602 use tcolor as tc;
603
604 assert_eq!(
605 Style::from_termion(Bg(tc::Reset)),
606 Style::new().bg(Color::Reset)
607 );
608 assert_eq!(Style::from_termion(Bg(tc::Black)), Style::new().on_black());
609 assert_eq!(Style::from_termion(Bg(tc::Red)), Style::new().on_red());
610 assert_eq!(Style::from_termion(Bg(tc::Green)), Style::new().on_green());
611 assert_eq!(
612 Style::from_termion(Bg(tc::Yellow)),
613 Style::new().on_yellow()
614 );
615 assert_eq!(Style::from_termion(Bg(tc::Blue)), Style::new().on_blue());
616 assert_eq!(
617 Style::from_termion(Bg(tc::Magenta)),
618 Style::new().on_magenta()
619 );
620 assert_eq!(Style::from_termion(Bg(tc::Cyan)), Style::new().on_cyan());
621 assert_eq!(Style::from_termion(Bg(tc::White)), Style::new().on_gray());
622 assert_eq!(
623 Style::from_termion(Bg(tc::LightBlack)),
624 Style::new().on_dark_gray()
625 );
626 assert_eq!(
627 Style::from_termion(Bg(tc::LightRed)),
628 Style::new().on_light_red()
629 );
630 assert_eq!(
631 Style::from_termion(Bg(tc::LightGreen)),
632 Style::new().on_light_green()
633 );
634 assert_eq!(
635 Style::from_termion(Bg(tc::LightBlue)),
636 Style::new().on_light_blue()
637 );
638 assert_eq!(
639 Style::from_termion(Bg(tc::LightYellow)),
640 Style::new().on_light_yellow()
641 );
642 assert_eq!(
643 Style::from_termion(Bg(tc::LightMagenta)),
644 Style::new().on_light_magenta()
645 );
646 assert_eq!(
647 Style::from_termion(Bg(tc::LightCyan)),
648 Style::new().on_light_cyan()
649 );
650 assert_eq!(
651 Style::from_termion(Bg(tc::LightWhite)),
652 Style::new().on_white()
653 );
654 assert_eq!(
655 Style::from_termion(Bg(tc::AnsiValue(31))),
656 Style::new().bg(Color::Indexed(31))
657 );
658 assert_eq!(
659 Style::from_termion(Bg(tc::Rgb(1, 2, 3))),
660 Style::new().bg(Color::Rgb(1, 2, 3))
661 );
662 }
663
664 #[test]
665 fn from_termion_fg() {
666 use tc::Fg;
667 use tcolor as tc;
668
669 assert_eq!(
670 Style::from_termion(Fg(tc::Reset)),
671 Style::new().fg(Color::Reset)
672 );
673 assert_eq!(Style::from_termion(Fg(tc::Black)), Style::new().black());
674 assert_eq!(Style::from_termion(Fg(tc::Red)), Style::new().red());
675 assert_eq!(Style::from_termion(Fg(tc::Green)), Style::new().green());
676 assert_eq!(Style::from_termion(Fg(tc::Yellow)), Style::new().yellow());
677 assert_eq!(Style::from_termion(Fg(tc::Blue)), Style::default().blue());
678 assert_eq!(
679 Style::from_termion(Fg(tc::Magenta)),
680 Style::default().magenta()
681 );
682 assert_eq!(Style::from_termion(Fg(tc::Cyan)), Style::default().cyan());
683 assert_eq!(Style::from_termion(Fg(tc::White)), Style::default().gray());
684 assert_eq!(
685 Style::from_termion(Fg(tc::LightBlack)),
686 Style::new().dark_gray()
687 );
688 assert_eq!(
689 Style::from_termion(Fg(tc::LightRed)),
690 Style::new().light_red()
691 );
692 assert_eq!(
693 Style::from_termion(Fg(tc::LightGreen)),
694 Style::new().light_green()
695 );
696 assert_eq!(
697 Style::from_termion(Fg(tc::LightBlue)),
698 Style::new().light_blue()
699 );
700 assert_eq!(
701 Style::from_termion(Fg(tc::LightYellow)),
702 Style::new().light_yellow()
703 );
704 assert_eq!(
705 Style::from_termion(Fg(tc::LightMagenta)),
706 Style::new().light_magenta()
707 );
708 assert_eq!(
709 Style::from_termion(Fg(tc::LightCyan)),
710 Style::new().light_cyan()
711 );
712 assert_eq!(
713 Style::from_termion(Fg(tc::LightWhite)),
714 Style::new().white()
715 );
716 assert_eq!(
717 Style::from_termion(Fg(tc::AnsiValue(31))),
718 Style::default().fg(Color::Indexed(31))
719 );
720 assert_eq!(
721 Style::from_termion(Fg(tc::Rgb(1, 2, 3))),
722 Style::default().fg(Color::Rgb(1, 2, 3))
723 );
724 }
725
726 #[test]
727 fn from_termion_style() {
728 assert_eq!(Modifier::from_termion(tstyle::Invert), Modifier::REVERSED);
729 assert_eq!(Modifier::from_termion(tstyle::Bold), Modifier::BOLD);
730 assert_eq!(Modifier::from_termion(tstyle::Italic), Modifier::ITALIC);
731 assert_eq!(
732 Modifier::from_termion(tstyle::Underline),
733 Modifier::UNDERLINED
734 );
735 assert_eq!(Modifier::from_termion(tstyle::Faint), Modifier::DIM);
736 assert_eq!(
737 Modifier::from_termion(tstyle::CrossedOut),
738 Modifier::CROSSED_OUT
739 );
740 assert_eq!(Modifier::from_termion(tstyle::Blink), Modifier::SLOW_BLINK);
741 assert_eq!(Modifier::from_termion(tstyle::Reset), Modifier::empty());
742 }
743}