display/
crossterm.rs

1use std::io::{stdout, BufWriter, Stdout, Write};
2
3use crossterm::{
4	cursor::{Hide, MoveTo, MoveToColumn, MoveToNextLine, Show},
5	event::{
6		DisableMouseCapture,
7		EnableMouseCapture,
8		KeyboardEnhancementFlags,
9		PopKeyboardEnhancementFlags,
10		PushKeyboardEnhancementFlags,
11	},
12	style::{available_color_count, Attribute, Colors, Print, ResetColor, SetAttribute, SetColors},
13	terminal::{
14		disable_raw_mode,
15		enable_raw_mode,
16		size,
17		Clear,
18		ClearType,
19		DisableLineWrap,
20		EnableLineWrap,
21		EnterAlternateScreen,
22		LeaveAlternateScreen,
23	},
24	Command,
25	QueueableCommand,
26};
27
28use super::{color_mode::ColorMode, size::Size, tui::Tui, utils::detect_color_mode};
29use crate::error::DisplayError;
30
31/// A thin wrapper over the [Crossterm library](https://github.com/crossterm-rs/crossterm).
32#[derive(Debug)]
33pub struct CrossTerm {
34	color_mode: ColorMode,
35	window: BufWriter<Stdout>,
36}
37
38impl Tui for CrossTerm {
39	#[inline]
40	fn get_color_mode(&self) -> ColorMode {
41		self.color_mode
42	}
43
44	#[inline]
45	fn reset(&mut self) -> Result<(), DisplayError> {
46		self.queue_command(ResetColor)?;
47		self.queue_command(SetAttribute(Attribute::Reset))?;
48		self.queue_command(Clear(ClearType::All))?;
49		self.queue_command(MoveTo(0, 0))
50	}
51
52	#[inline]
53	fn flush(&mut self) -> Result<(), DisplayError> {
54		self.window.flush().map_err(DisplayError::Unexpected)
55	}
56
57	#[inline]
58	fn print(&mut self, s: &str) -> Result<(), DisplayError> {
59		self.queue_command(Print(s))
60	}
61
62	#[inline]
63	fn set_color(&mut self, colors: Colors) -> Result<(), DisplayError> {
64		self.queue_command(SetColors(colors))
65	}
66
67	#[inline]
68	fn set_dim(&mut self, dim: bool) -> Result<(), DisplayError> {
69		self.queue_command(SetAttribute(
70			if dim {
71				Attribute::Dim
72			}
73			else {
74				Attribute::NormalIntensity
75			},
76		))
77	}
78
79	#[inline]
80	fn set_underline(&mut self, underline: bool) -> Result<(), DisplayError> {
81		self.queue_command(SetAttribute(
82			if underline {
83				Attribute::Underlined
84			}
85			else {
86				Attribute::NoUnderline
87			},
88		))
89	}
90
91	#[inline]
92	fn set_reverse(&mut self, reverse: bool) -> Result<(), DisplayError> {
93		self.queue_command(SetAttribute(
94			if reverse {
95				Attribute::Reverse
96			}
97			else {
98				Attribute::NoReverse
99			},
100		))
101	}
102
103	#[inline]
104	fn get_size(&self) -> Size {
105		size().map_or_else(
106			|_| Size::new(0, 0),
107			|(width, height)| Size::new(usize::from(width), usize::from(height)),
108		)
109	}
110
111	#[inline]
112	fn move_to_column(&mut self, x: u16) -> Result<(), DisplayError> {
113		self.queue_command(MoveToColumn(x))
114	}
115
116	#[inline]
117	fn move_next_line(&mut self) -> Result<(), DisplayError> {
118		self.queue_command(MoveToNextLine(1))
119	}
120
121	#[inline]
122	fn start(&mut self) -> Result<(), DisplayError> {
123		self.queue_command(EnterAlternateScreen)?;
124		self.queue_command(DisableLineWrap)?;
125		self.queue_command(Hide)?;
126		self.queue_command(EnableMouseCapture)?;
127		// this will fail on terminals without support, so ignore any errors
128		let _command_result = self.queue_command(PushKeyboardEnhancementFlags(
129			KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES
130				| KeyboardEnhancementFlags::REPORT_EVENT_TYPES
131				| KeyboardEnhancementFlags::REPORT_ALTERNATE_KEYS
132				| KeyboardEnhancementFlags::REPORT_ALL_KEYS_AS_ESCAPE_CODES,
133		));
134		enable_raw_mode().map_err(DisplayError::Unexpected)?;
135		self.flush()
136	}
137
138	#[inline]
139	fn end(&mut self) -> Result<(), DisplayError> {
140		// this will fail on terminals without support, so ignore any errors
141		let _command_result = self.queue_command(PopKeyboardEnhancementFlags);
142		self.queue_command(DisableMouseCapture)?;
143		self.queue_command(Show)?;
144		self.queue_command(EnableLineWrap)?;
145		self.queue_command(LeaveAlternateScreen)?;
146		disable_raw_mode().map_err(DisplayError::Unexpected)?;
147		self.flush()
148	}
149}
150
151impl CrossTerm {
152	/// Create a new instance.
153	#[inline]
154	#[must_use]
155	pub fn new() -> Self {
156		Self {
157			window: BufWriter::new(stdout()),
158			color_mode: detect_color_mode(available_color_count()),
159		}
160	}
161
162	fn queue_command(&mut self, command: impl Command) -> Result<(), DisplayError> {
163		let _result = self.window.queue(command).map_err(DisplayError::Unexpected)?;
164		Ok(())
165	}
166}