1use crate::console::{Console, ConsoleOptions};
11use crate::protocol::Renderable;
12use crate::segment::Segment;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum ControlType {
21 Bell,
22 CarriageReturn,
23 Home,
24 Clear,
25 ShowCursor,
26 HideCursor,
27 EnableAltScreen,
28 DisableAltScreen,
29 CursorUp(u32),
30 CursorDown(u32),
31 CursorForward(u32),
32 CursorBackward(u32),
33 CursorMoveToColumn(u32),
35 CursorMoveTo(u32, u32),
37 EraseInLine(u32),
39}
40
41impl ControlType {
42 fn format(self) -> String {
44 match self {
45 ControlType::Bell => "\x07".to_string(),
46 ControlType::CarriageReturn => "\r".to_string(),
47 ControlType::Home => "\x1b[H".to_string(),
48 ControlType::Clear => "\x1b[2J".to_string(),
49 ControlType::EnableAltScreen => "\x1b[?1049h".to_string(),
50 ControlType::DisableAltScreen => "\x1b[?1049l".to_string(),
51 ControlType::ShowCursor => "\x1b[?25h".to_string(),
52 ControlType::HideCursor => "\x1b[?25l".to_string(),
53 ControlType::CursorUp(n) => format!("\x1b[{n}A"),
54 ControlType::CursorDown(n) => format!("\x1b[{n}B"),
55 ControlType::CursorForward(n) => format!("\x1b[{n}C"),
56 ControlType::CursorBackward(n) => format!("\x1b[{n}D"),
57 ControlType::CursorMoveToColumn(x) => format!("\x1b[{}G", u64::from(x) + 1),
60 ControlType::CursorMoveTo(x, y) => move_to_code(u128::from(x), u128::from(y)),
61 ControlType::EraseInLine(n) => format!("\x1b[{n}K"),
62 }
63 }
64}
65
66pub(crate) fn move_to_code(x: u128, y: u128) -> String {
73 format!("\x1b[{};{}H", y + 1, x + 1)
74}
75
76pub struct Control {
82 segment: Segment,
83}
84
85impl Control {
86 pub fn new(codes: &[ControlType]) -> Self {
88 let text: String = codes.iter().map(|c| c.format()).collect();
89 Control {
90 segment: Segment::control(text),
91 }
92 }
93
94 fn single(code: ControlType) -> Self {
95 Control::new(&[code])
96 }
97
98 pub fn bell() -> Self {
100 Control::single(ControlType::Bell)
101 }
102
103 pub fn home() -> Self {
105 Control::single(ControlType::Home)
106 }
107
108 pub fn clear() -> Self {
110 Control::single(ControlType::Clear)
111 }
112
113 #[allow(clippy::should_implement_trait)]
116 pub fn move_(x: i32, y: i32) -> Self {
117 let mut codes = Vec::new();
118 if x != 0 {
119 codes.push(if x > 0 {
120 ControlType::CursorForward(x.unsigned_abs())
121 } else {
122 ControlType::CursorBackward(x.unsigned_abs())
123 });
124 }
125 if y != 0 {
126 codes.push(if y > 0 {
127 ControlType::CursorDown(y.unsigned_abs())
128 } else {
129 ControlType::CursorUp(y.unsigned_abs())
130 });
131 }
132 Control::new(&codes)
133 }
134
135 pub fn move_to_column(x: u32, y: i32) -> Self {
138 if y != 0 {
139 let vertical = if y > 0 {
140 ControlType::CursorDown(y.unsigned_abs())
141 } else {
142 ControlType::CursorUp(y.unsigned_abs())
143 };
144 Control::new(&[ControlType::CursorMoveToColumn(x), vertical])
145 } else {
146 Control::single(ControlType::CursorMoveToColumn(x))
147 }
148 }
149
150 pub fn move_to(x: u32, y: u32) -> Self {
152 Control::single(ControlType::CursorMoveTo(x, y))
153 }
154
155 pub fn show_cursor(show: bool) -> Self {
157 Control::single(if show {
158 ControlType::ShowCursor
159 } else {
160 ControlType::HideCursor
161 })
162 }
163
164 pub fn alt_screen(enable: bool) -> Self {
167 if enable {
168 Control::new(&[ControlType::EnableAltScreen, ControlType::Home])
169 } else {
170 Control::single(ControlType::DisableAltScreen)
171 }
172 }
173
174 pub fn title(title: &str) -> Self {
177 Control {
178 segment: Segment::control(format!("\x1b]0;{title}\x07")),
179 }
180 }
181
182 pub fn as_str(&self) -> &str {
184 &self.segment.text
185 }
186}
187
188impl Renderable for Control {
189 fn rich_render(&self, _console: &Console, _options: &ConsoleOptions) -> Vec<Segment> {
190 if self.segment.text.is_empty() {
191 Vec::new()
192 } else {
193 vec![self.segment.clone()]
194 }
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201
202 #[test]
204 fn escape_strings_match_upstream() {
205 assert_eq!(Control::clear().as_str(), "\x1b[2J");
206 assert_eq!(Control::home().as_str(), "\x1b[H");
207 assert_eq!(Control::bell().as_str(), "\x07");
208 assert_eq!(Control::show_cursor(true).as_str(), "\x1b[?25h");
209 assert_eq!(Control::show_cursor(false).as_str(), "\x1b[?25l");
210 assert_eq!(Control::move_(2, -1).as_str(), "\x1b[2C\x1b[1A");
211 assert_eq!(Control::move_to(3, 4).as_str(), "\x1b[5;4H");
212 assert_eq!(Control::move_to_column(5, 0).as_str(), "\x1b[6G");
213 assert_eq!(Control::alt_screen(true).as_str(), "\x1b[?1049h\x1b[H");
214 assert_eq!(Control::title("my title").as_str(), "\x1b]0;my title\x07");
215 assert_eq!(Control::alt_screen(false).as_str(), "\x1b[?1049l");
216 }
217
218 #[test]
219 fn renders_control_segment() {
220 let control = Control::clear();
221 let segments = control.rich_render(&Console::new(), &Console::new().options());
222 assert_eq!(segments.len(), 1);
223 assert!(segments[0].control);
224 assert_eq!(segments[0].cell_length(), 0);
225 }
226}