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", x + 1),
58 ControlType::CursorMoveTo(x, y) => format!("\x1b[{};{}H", y + 1, x + 1),
59 ControlType::EraseInLine(n) => format!("\x1b[{n}K"),
60 }
61 }
62}
63
64pub struct Control {
70 segment: Segment,
71}
72
73impl Control {
74 pub fn new(codes: &[ControlType]) -> Self {
76 let text: String = codes.iter().map(|c| c.format()).collect();
77 Control {
78 segment: Segment::control(text),
79 }
80 }
81
82 fn single(code: ControlType) -> Self {
83 Control::new(&[code])
84 }
85
86 pub fn bell() -> Self {
88 Control::single(ControlType::Bell)
89 }
90
91 pub fn home() -> Self {
93 Control::single(ControlType::Home)
94 }
95
96 pub fn clear() -> Self {
98 Control::single(ControlType::Clear)
99 }
100
101 #[allow(clippy::should_implement_trait)]
104 pub fn move_(x: i32, y: i32) -> Self {
105 let mut codes = Vec::new();
106 if x != 0 {
107 codes.push(if x > 0 {
108 ControlType::CursorForward(x.unsigned_abs())
109 } else {
110 ControlType::CursorBackward(x.unsigned_abs())
111 });
112 }
113 if y != 0 {
114 codes.push(if y > 0 {
115 ControlType::CursorDown(y.unsigned_abs())
116 } else {
117 ControlType::CursorUp(y.unsigned_abs())
118 });
119 }
120 Control::new(&codes)
121 }
122
123 pub fn move_to_column(x: u32, y: i32) -> Self {
126 if y != 0 {
127 let vertical = if y > 0 {
128 ControlType::CursorDown(y.unsigned_abs())
129 } else {
130 ControlType::CursorUp(y.unsigned_abs())
131 };
132 Control::new(&[ControlType::CursorMoveToColumn(x), vertical])
133 } else {
134 Control::single(ControlType::CursorMoveToColumn(x))
135 }
136 }
137
138 pub fn move_to(x: u32, y: u32) -> Self {
140 Control::single(ControlType::CursorMoveTo(x, y))
141 }
142
143 pub fn show_cursor(show: bool) -> Self {
145 Control::single(if show {
146 ControlType::ShowCursor
147 } else {
148 ControlType::HideCursor
149 })
150 }
151
152 pub fn alt_screen(enable: bool) -> Self {
154 Control::single(if enable {
155 ControlType::EnableAltScreen
156 } else {
157 ControlType::DisableAltScreen
158 })
159 }
160
161 pub fn as_str(&self) -> &str {
163 &self.segment.text
164 }
165}
166
167impl Renderable for Control {
168 fn rich_render(&self, _console: &Console, _options: &ConsoleOptions) -> Vec<Segment> {
169 if self.segment.text.is_empty() {
170 Vec::new()
171 } else {
172 vec![self.segment.clone()]
173 }
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180
181 #[test]
183 fn escape_strings_match_upstream() {
184 assert_eq!(Control::clear().as_str(), "\x1b[2J");
185 assert_eq!(Control::home().as_str(), "\x1b[H");
186 assert_eq!(Control::bell().as_str(), "\x07");
187 assert_eq!(Control::show_cursor(true).as_str(), "\x1b[?25h");
188 assert_eq!(Control::show_cursor(false).as_str(), "\x1b[?25l");
189 assert_eq!(Control::move_(2, -1).as_str(), "\x1b[2C\x1b[1A");
190 assert_eq!(Control::move_to(3, 4).as_str(), "\x1b[5;4H");
191 assert_eq!(Control::move_to_column(5, 0).as_str(), "\x1b[6G");
192 assert_eq!(Control::alt_screen(true).as_str(), "\x1b[?1049h");
193 assert_eq!(Control::alt_screen(false).as_str(), "\x1b[?1049l");
194 }
195
196 #[test]
197 fn renders_control_segment() {
198 let control = Control::clear();
199 let segments = control.rich_render(&Console::new(), &Console::new().options());
200 assert_eq!(segments.len(), 1);
201 assert!(segments[0].control);
202 assert_eq!(segments[0].cell_length(), 0);
203 }
204}