1pub const CLEAR_HOME: &str = "\x1b[2J\x1b[H";
24pub const CLEAR_SCREEN: &str = "\x1b[2J";
26pub const CURSOR_HOME: &str = "\x1b[H";
28pub const CURSOR_SHOW: &str = "\x1b[?25h";
30pub const CURSOR_HIDE: &str = "\x1b[?25l";
32pub const ALT_SCREEN_ENTER: &str = "\x1b[?1049h";
34pub const ALT_SCREEN_EXIT: &str = "\x1b[?1049l";
36pub const ERASE_LINE: &str = "\x1b[2K";
38pub const CURSOR_UP: &str = "\x1b[1A";
40pub const CARRIAGE_RETURN: &str = "\r";
42pub const NEWLINE: &str = "\n";
44pub const OSC: &str = "\x1b]";
46pub const ST: &str = "\x07";
48
49#[derive(Debug, Clone)]
75pub struct Control {
76 sequences: Vec<String>,
77}
78
79impl Control {
80 pub fn new(sequences: &[&str]) -> Self {
82 Self {
83 sequences: sequences.iter().map(|s| s.to_string()).collect(),
84 }
85 }
86
87 pub fn bell() -> Self {
89 Self::new(&["\x07"])
90 }
91
92 pub fn home() -> Self {
94 Self::new(&["\x1b[H"])
95 }
96
97 pub fn clear() -> Self {
99 Self::new(&["\x1b[2J"])
100 }
101
102 pub fn clear_home() -> Self {
104 Self::new(&["\x1b[2J", "\x1b[H"])
105 }
106
107 pub fn cursor_up(n: u16) -> Self {
109 Self::new(&[&format!("\x1b[{n}A")])
110 }
111
112 pub fn cursor_down(n: u16) -> Self {
114 Self::new(&[&format!("\x1b[{n}B")])
115 }
116
117 pub fn cursor_forward(n: u16) -> Self {
119 Self::new(&[&format!("\x1b[{n}C")])
120 }
121
122 pub fn cursor_back(n: u16) -> Self {
124 Self::new(&[&format!("\x1b[{n}D")])
125 }
126
127 pub fn cursor_to(row: u16, col: u16) -> Self {
129 Self::new(&[&format!("\x1b[{row};{col}H")])
130 }
131
132 pub fn cursor_to_row(row: u16) -> Self {
134 Self::new(&[&format!("\x1b[{row}d")])
135 }
136
137 pub fn cursor_to_column(col: u16) -> Self {
139 Self::new(&[&format!("\x1b[{col}G")])
140 }
141
142 pub fn alt_screen(enable: bool) -> Self {
144 if enable {
145 Self::new(&["\x1b[?1049h"])
146 } else {
147 Self::new(&["\x1b[?1049l"])
148 }
149 }
150
151 pub fn show_cursor(show: bool) -> Self {
153 if show {
154 Self::new(&["\x1b[?25h"])
155 } else {
156 Self::new(&["\x1b[?25l"])
157 }
158 }
159
160 pub fn title(title: impl Into<String>) -> Self {
162 let t: String = title.into();
163 Self::new(&[&format!("\x1b]0;{t}\x07")])
164 }
165
166 pub fn erase_end_line() -> Self {
168 Self::new(&["\x1b[K"])
169 }
170
171 pub fn erase_start_line() -> Self {
173 Self::new(&["\x1b[1K"])
174 }
175
176 pub fn erase_line() -> Self {
178 Self::new(&["\x1b[2K"])
179 }
180
181 pub fn erase_end_screen() -> Self {
183 Self::new(&["\x1b[J"])
184 }
185
186 pub fn erase_start_screen() -> Self {
188 Self::new(&["\x1b[1J"])
189 }
190
191 pub fn insert_lines(n: u16) -> Self {
193 Self::new(&[&format!("\x1b[{n}L")])
194 }
195
196 pub fn delete_lines(n: u16) -> Self {
198 Self::new(&[&format!("\x1b[{n}M")])
199 }
200
201 pub fn carriage_return() -> Self {
203 Self::new(&["\r"])
204 }
205
206 pub fn newline() -> Self {
208 Self::new(&["\n"])
209 }
210
211 pub fn to_ansi(&self) -> String {
213 self.sequences.concat()
214 }
215
216 pub fn sequences(&self) -> &[String] {
218 &self.sequences
219 }
220
221 pub fn len(&self) -> usize {
223 self.sequences.len()
224 }
225
226 pub fn is_empty(&self) -> bool {
228 self.sequences.is_empty()
229 }
230}
231
232pub fn control_bell() -> Control {
238 Control::bell()
239}
240
241pub fn control_home() -> Control {
243 Control::home()
244}
245
246pub fn control_clear() -> Control {
248 Control::clear()
249}
250
251pub fn control_move_to(row: u16, col: u16) -> Control {
253 Control::cursor_to(row, col)
254}
255
256pub fn control_show_cursor(show: bool) -> Control {
258 Control::show_cursor(show)
259}
260
261pub fn control_title(title: impl Into<String>) -> Control {
263 Control::title(title)
264}
265
266pub fn strip_control_codes(text: &str) -> String {
275 text.chars()
276 .filter(|&c| !matches!(c, '\x07' | '\x08' | '\x0b' | '\x0c'))
277 .collect()
278}
279
280pub fn escape_control_codes(text: &str) -> String {
283 text.chars()
284 .map(|c| match c {
285 '\x07' => "\\a".to_string(),
286 '\x08' => "\\b".to_string(),
287 '\x0b' => "\\v".to_string(),
288 '\x0c' => "\\f".to_string(),
289 '\r' => "\\r".to_string(),
290 '\n' => "\\n".to_string(),
291 '\t' => "\\t".to_string(),
292 other => other.to_string(),
293 })
294 .collect()
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300
301 #[test]
302 fn test_control_bell() {
303 let c = Control::bell();
304 assert_eq!(c.to_ansi(), "\x07");
305 }
306
307 #[test]
308 fn test_control_home() {
309 let c = Control::home();
310 assert_eq!(c.to_ansi(), "\x1b[H");
311 }
312
313 #[test]
314 fn test_control_clear() {
315 let c = Control::clear();
316 assert_eq!(c.to_ansi(), "\x1b[2J");
317 }
318
319 #[test]
320 fn test_control_clear_home() {
321 let c = Control::clear_home();
322 assert_eq!(c.to_ansi(), "\x1b[2J\x1b[H");
323 }
324
325 #[test]
326 fn test_control_cursor_to() {
327 let c = Control::cursor_to(10, 5);
328 assert_eq!(c.to_ansi(), "\x1b[10;5H");
329 }
330
331 #[test]
332 fn test_control_cursor_up() {
333 let c = Control::cursor_up(5);
334 assert_eq!(c.to_ansi(), "\x1b[5A");
335 }
336
337 #[test]
338 fn test_control_show_cursor() {
339 let c = Control::show_cursor(true);
340 assert_eq!(c.to_ansi(), "\x1b[?25h");
341 let c = Control::show_cursor(false);
342 assert_eq!(c.to_ansi(), "\x1b[?25l");
343 }
344
345 #[test]
346 fn test_control_alt_screen() {
347 let c = Control::alt_screen(true);
348 assert_eq!(c.to_ansi(), "\x1b[?1049h");
349 let c = Control::alt_screen(false);
350 assert_eq!(c.to_ansi(), "\x1b[?1049l");
351 }
352
353 #[test]
354 fn test_control_title() {
355 let c = Control::title("My App");
356 assert_eq!(c.to_ansi(), "\x1b]0;My App\x07");
357 }
358
359 #[test]
360 fn test_control_erase() {
361 assert_eq!(Control::erase_line().to_ansi(), "\x1b[2K");
362 assert_eq!(Control::erase_end_line().to_ansi(), "\x1b[K");
363 assert_eq!(Control::erase_start_line().to_ansi(), "\x1b[1K");
364 }
365
366 #[test]
367 fn test_control_insert_delete_lines() {
368 assert_eq!(Control::insert_lines(3).to_ansi(), "\x1b[3L");
369 assert_eq!(Control::delete_lines(2).to_ansi(), "\x1b[2M");
370 }
371
372 #[test]
373 fn test_control_len_empty() {
374 assert_eq!(Control::bell().len(), 1);
375 assert_eq!(Control::clear_home().len(), 2);
376 assert!(!Control::bell().is_empty());
377 }
378
379 #[test]
380 fn test_strip_control_codes() {
381 assert_eq!(strip_control_codes("hello\x07world"), "helloworld");
382 assert_eq!(strip_control_codes("normal text"), "normal text");
383 }
384
385 #[test]
386 fn test_escape_control_codes() {
387 let result = escape_control_codes("\x07\x08\x0b\x0c");
388 assert_eq!(result, "\\a\\b\\v\\f");
389 }
390
391 #[test]
392 fn test_convenience_functions() {
393 assert_eq!(control_bell().to_ansi(), "\x07");
394 assert_eq!(control_home().to_ansi(), "\x1b[H");
395 assert_eq!(control_move_to(3, 8).to_ansi(), "\x1b[3;8H");
396 }
397}