#[derive(Debug, Clone)]
pub struct Control {
sequences: Vec<String>,
}
impl Control {
pub fn new(sequences: &[&str]) -> Self {
Self {
sequences: sequences.iter().map(|s| s.to_string()).collect(),
}
}
pub fn bell() -> Self {
Self::new(&["\x07"])
}
pub fn home() -> Self {
Self::new(&["\x1b[H"])
}
pub fn clear() -> Self {
Self::new(&["\x1b[2J"])
}
pub fn clear_home() -> Self {
Self::new(&["\x1b[2J", "\x1b[H"])
}
pub fn cursor_up(n: u16) -> Self {
Self::new(&[&format!("\x1b[{n}A")])
}
pub fn cursor_down(n: u16) -> Self {
Self::new(&[&format!("\x1b[{n}B")])
}
pub fn cursor_forward(n: u16) -> Self {
Self::new(&[&format!("\x1b[{n}C")])
}
pub fn cursor_back(n: u16) -> Self {
Self::new(&[&format!("\x1b[{n}D")])
}
pub fn cursor_to(row: u16, col: u16) -> Self {
Self::new(&[&format!("\x1b[{row};{col}H")])
}
pub fn cursor_to_row(row: u16) -> Self {
Self::new(&[&format!("\x1b[{row}d")])
}
pub fn cursor_to_column(col: u16) -> Self {
Self::new(&[&format!("\x1b[{col}G")])
}
pub fn alt_screen(enable: bool) -> Self {
if enable {
Self::new(&["\x1b[?1049h"])
} else {
Self::new(&["\x1b[?1049l"])
}
}
pub fn show_cursor(show: bool) -> Self {
if show {
Self::new(&["\x1b[?25h"])
} else {
Self::new(&["\x1b[?25l"])
}
}
pub fn title(title: impl Into<String>) -> Self {
let t: String = title.into();
Self::new(&[&format!("\x1b]0;{t}\x07")])
}
pub fn erase_end_line() -> Self {
Self::new(&["\x1b[K"])
}
pub fn erase_start_line() -> Self {
Self::new(&["\x1b[1K"])
}
pub fn erase_line() -> Self {
Self::new(&["\x1b[2K"])
}
pub fn erase_end_screen() -> Self {
Self::new(&["\x1b[J"])
}
pub fn erase_start_screen() -> Self {
Self::new(&["\x1b[1J"])
}
pub fn insert_lines(n: u16) -> Self {
Self::new(&[&format!("\x1b[{n}L")])
}
pub fn delete_lines(n: u16) -> Self {
Self::new(&[&format!("\x1b[{n}M")])
}
pub fn carriage_return() -> Self {
Self::new(&["\r"])
}
pub fn newline() -> Self {
Self::new(&["\n"])
}
pub fn to_ansi(&self) -> String {
self.sequences.concat()
}
pub fn sequences(&self) -> &[String] {
&self.sequences
}
pub fn len(&self) -> usize {
self.sequences.len()
}
pub fn is_empty(&self) -> bool {
self.sequences.is_empty()
}
}
pub fn control_bell() -> Control { Control::bell() }
pub fn control_home() -> Control { Control::home() }
pub fn control_clear() -> Control { Control::clear() }
pub fn control_move_to(row: u16, col: u16) -> Control { Control::cursor_to(row, col) }
pub fn control_show_cursor(show: bool) -> Control { Control::show_cursor(show) }
pub fn control_title(title: impl Into<String>) -> Control { Control::title(title) }
pub fn strip_control_codes(text: &str) -> String {
text.chars()
.filter(|&c| !matches!(c, '\x07' | '\x08' | '\x0b' | '\x0c'))
.collect()
}
pub fn escape_control_codes(text: &str) -> String {
text.chars()
.map(|c| match c {
'\x07' => "\\a".to_string(),
'\x08' => "\\b".to_string(),
'\x0b' => "\\v".to_string(),
'\x0c' => "\\f".to_string(),
'\r' => "\\r".to_string(),
'\n' => "\\n".to_string(),
'\t' => "\\t".to_string(),
other => other.to_string(),
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_control_bell() {
let c = Control::bell();
assert_eq!(c.to_ansi(), "\x07");
}
#[test]
fn test_control_home() {
let c = Control::home();
assert_eq!(c.to_ansi(), "\x1b[H");
}
#[test]
fn test_control_clear() {
let c = Control::clear();
assert_eq!(c.to_ansi(), "\x1b[2J");
}
#[test]
fn test_control_clear_home() {
let c = Control::clear_home();
assert_eq!(c.to_ansi(), "\x1b[2J\x1b[H");
}
#[test]
fn test_control_cursor_to() {
let c = Control::cursor_to(10, 5);
assert_eq!(c.to_ansi(), "\x1b[10;5H");
}
#[test]
fn test_control_cursor_up() {
let c = Control::cursor_up(5);
assert_eq!(c.to_ansi(), "\x1b[5A");
}
#[test]
fn test_control_show_cursor() {
let c = Control::show_cursor(true);
assert_eq!(c.to_ansi(), "\x1b[?25h");
let c = Control::show_cursor(false);
assert_eq!(c.to_ansi(), "\x1b[?25l");
}
#[test]
fn test_control_alt_screen() {
let c = Control::alt_screen(true);
assert_eq!(c.to_ansi(), "\x1b[?1049h");
let c = Control::alt_screen(false);
assert_eq!(c.to_ansi(), "\x1b[?1049l");
}
#[test]
fn test_control_title() {
let c = Control::title("My App");
assert_eq!(c.to_ansi(), "\x1b]0;My App\x07");
}
#[test]
fn test_control_erase() {
assert_eq!(Control::erase_line().to_ansi(), "\x1b[2K");
assert_eq!(Control::erase_end_line().to_ansi(), "\x1b[K");
assert_eq!(Control::erase_start_line().to_ansi(), "\x1b[1K");
}
#[test]
fn test_control_insert_delete_lines() {
assert_eq!(Control::insert_lines(3).to_ansi(), "\x1b[3L");
assert_eq!(Control::delete_lines(2).to_ansi(), "\x1b[2M");
}
#[test]
fn test_control_len_empty() {
assert_eq!(Control::bell().len(), 1);
assert_eq!(Control::clear_home().len(), 2);
assert!(!Control::bell().is_empty());
}
#[test]
fn test_strip_control_codes() {
assert_eq!(strip_control_codes("hello\x07world"), "helloworld");
assert_eq!(strip_control_codes("normal text"), "normal text");
}
#[test]
fn test_escape_control_codes() {
let result = escape_control_codes("\x07\x08\x0b\x0c");
assert_eq!(result, "\\a\\b\\v\\f");
}
#[test]
fn test_convenience_functions() {
assert_eq!(control_bell().to_ansi(), "\x07");
assert_eq!(control_home().to_ansi(), "\x1b[H");
assert_eq!(control_move_to(3, 8).to_ansi(), "\x1b[3;8H");
}
}