1#[derive(Debug, Clone)]
44pub struct Control {
45 sequences: Vec<String>,
46}
47
48impl Control {
49 pub fn new(sequences: &[&str]) -> Self {
51 Self {
52 sequences: sequences.iter().map(|s| s.to_string()).collect(),
53 }
54 }
55
56 pub fn bell() -> Self {
58 Self::new(&["\x07"])
59 }
60
61 pub fn home() -> Self {
63 Self::new(&["\x1b[H"])
64 }
65
66 pub fn clear() -> Self {
68 Self::new(&["\x1b[2J"])
69 }
70
71 pub fn clear_home() -> Self {
73 Self::new(&["\x1b[2J", "\x1b[H"])
74 }
75
76 pub fn cursor_up(n: u16) -> Self {
78 Self::new(&[&format!("\x1b[{n}A")])
79 }
80
81 pub fn cursor_down(n: u16) -> Self {
83 Self::new(&[&format!("\x1b[{n}B")])
84 }
85
86 pub fn cursor_forward(n: u16) -> Self {
88 Self::new(&[&format!("\x1b[{n}C")])
89 }
90
91 pub fn cursor_back(n: u16) -> Self {
93 Self::new(&[&format!("\x1b[{n}D")])
94 }
95
96 pub fn cursor_to(row: u16, col: u16) -> Self {
98 Self::new(&[&format!("\x1b[{row};{col}H")])
99 }
100
101 pub fn cursor_to_row(row: u16) -> Self {
103 Self::new(&[&format!("\x1b[{row}d")])
104 }
105
106 pub fn cursor_to_column(col: u16) -> Self {
108 Self::new(&[&format!("\x1b[{col}G")])
109 }
110
111 pub fn alt_screen(enable: bool) -> Self {
113 if enable {
114 Self::new(&["\x1b[?1049h"])
115 } else {
116 Self::new(&["\x1b[?1049l"])
117 }
118 }
119
120 pub fn show_cursor(show: bool) -> Self {
122 if show {
123 Self::new(&["\x1b[?25h"])
124 } else {
125 Self::new(&["\x1b[?25l"])
126 }
127 }
128
129 pub fn title(title: impl Into<String>) -> Self {
131 let t: String = title.into();
132 Self::new(&[&format!("\x1b]0;{t}\x07")])
133 }
134
135 pub fn erase_end_line() -> Self {
137 Self::new(&["\x1b[K"])
138 }
139
140 pub fn erase_start_line() -> Self {
142 Self::new(&["\x1b[1K"])
143 }
144
145 pub fn erase_line() -> Self {
147 Self::new(&["\x1b[2K"])
148 }
149
150 pub fn erase_end_screen() -> Self {
152 Self::new(&["\x1b[J"])
153 }
154
155 pub fn erase_start_screen() -> Self {
157 Self::new(&["\x1b[1J"])
158 }
159
160 pub fn insert_lines(n: u16) -> Self {
162 Self::new(&[&format!("\x1b[{n}L")])
163 }
164
165 pub fn delete_lines(n: u16) -> Self {
167 Self::new(&[&format!("\x1b[{n}M")])
168 }
169
170 pub fn carriage_return() -> Self {
172 Self::new(&["\r"])
173 }
174
175 pub fn newline() -> Self {
177 Self::new(&["\n"])
178 }
179
180 pub fn to_ansi(&self) -> String {
182 self.sequences.concat()
183 }
184
185 pub fn sequences(&self) -> &[String] {
187 &self.sequences
188 }
189
190 pub fn len(&self) -> usize {
192 self.sequences.len()
193 }
194
195 pub fn is_empty(&self) -> bool {
197 self.sequences.is_empty()
198 }
199}
200
201pub fn control_bell() -> Control { Control::bell() }
207
208pub fn control_home() -> Control { Control::home() }
210
211pub fn control_clear() -> Control { Control::clear() }
213
214pub fn control_move_to(row: u16, col: u16) -> Control { Control::cursor_to(row, col) }
216
217pub fn control_show_cursor(show: bool) -> Control { Control::show_cursor(show) }
219
220pub fn control_title(title: impl Into<String>) -> Control { Control::title(title) }
222
223pub fn strip_control_codes(text: &str) -> String {
232 text.chars()
233 .filter(|&c| !matches!(c, '\x07' | '\x08' | '\x0b' | '\x0c'))
234 .collect()
235}
236
237pub fn escape_control_codes(text: &str) -> String {
240 text.chars()
241 .map(|c| match c {
242 '\x07' => "\\a".to_string(),
243 '\x08' => "\\b".to_string(),
244 '\x0b' => "\\v".to_string(),
245 '\x0c' => "\\f".to_string(),
246 '\r' => "\\r".to_string(),
247 '\n' => "\\n".to_string(),
248 '\t' => "\\t".to_string(),
249 other => other.to_string(),
250 })
251 .collect()
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257
258 #[test]
259 fn test_control_bell() {
260 let c = Control::bell();
261 assert_eq!(c.to_ansi(), "\x07");
262 }
263
264 #[test]
265 fn test_control_home() {
266 let c = Control::home();
267 assert_eq!(c.to_ansi(), "\x1b[H");
268 }
269
270 #[test]
271 fn test_control_clear() {
272 let c = Control::clear();
273 assert_eq!(c.to_ansi(), "\x1b[2J");
274 }
275
276 #[test]
277 fn test_control_clear_home() {
278 let c = Control::clear_home();
279 assert_eq!(c.to_ansi(), "\x1b[2J\x1b[H");
280 }
281
282 #[test]
283 fn test_control_cursor_to() {
284 let c = Control::cursor_to(10, 5);
285 assert_eq!(c.to_ansi(), "\x1b[10;5H");
286 }
287
288 #[test]
289 fn test_control_cursor_up() {
290 let c = Control::cursor_up(5);
291 assert_eq!(c.to_ansi(), "\x1b[5A");
292 }
293
294 #[test]
295 fn test_control_show_cursor() {
296 let c = Control::show_cursor(true);
297 assert_eq!(c.to_ansi(), "\x1b[?25h");
298 let c = Control::show_cursor(false);
299 assert_eq!(c.to_ansi(), "\x1b[?25l");
300 }
301
302 #[test]
303 fn test_control_alt_screen() {
304 let c = Control::alt_screen(true);
305 assert_eq!(c.to_ansi(), "\x1b[?1049h");
306 let c = Control::alt_screen(false);
307 assert_eq!(c.to_ansi(), "\x1b[?1049l");
308 }
309
310 #[test]
311 fn test_control_title() {
312 let c = Control::title("My App");
313 assert_eq!(c.to_ansi(), "\x1b]0;My App\x07");
314 }
315
316 #[test]
317 fn test_control_erase() {
318 assert_eq!(Control::erase_line().to_ansi(), "\x1b[2K");
319 assert_eq!(Control::erase_end_line().to_ansi(), "\x1b[K");
320 assert_eq!(Control::erase_start_line().to_ansi(), "\x1b[1K");
321 }
322
323 #[test]
324 fn test_control_insert_delete_lines() {
325 assert_eq!(Control::insert_lines(3).to_ansi(), "\x1b[3L");
326 assert_eq!(Control::delete_lines(2).to_ansi(), "\x1b[2M");
327 }
328
329 #[test]
330 fn test_control_len_empty() {
331 assert_eq!(Control::bell().len(), 1);
332 assert_eq!(Control::clear_home().len(), 2);
333 assert!(!Control::bell().is_empty());
334 }
335
336 #[test]
337 fn test_strip_control_codes() {
338 assert_eq!(strip_control_codes("hello\x07world"), "helloworld");
339 assert_eq!(strip_control_codes("normal text"), "normal text");
340 }
341
342 #[test]
343 fn test_escape_control_codes() {
344 let result = escape_control_codes("\x07\x08\x0b\x0c");
345 assert_eq!(result, "\\a\\b\\v\\f");
346 }
347
348 #[test]
349 fn test_convenience_functions() {
350 assert_eq!(control_bell().to_ansi(), "\x07");
351 assert_eq!(control_home().to_ansi(), "\x1b[H");
352 assert_eq!(control_move_to(3, 8).to_ansi(), "\x1b[3;8H");
353 }
354}