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