pub const RESET: &str = "\x1b[0m";
pub const CLEAR_SCREEN: &str = "\x1b[2J";
pub const ERASE_SCROLLBACK: &str = "\x1b[3J";
pub const CLEAR_SCREEN_BELOW: &str = "\x1b[J";
pub const CLEAR_SCREEN_ABOVE: &str = "\x1b[1J";
pub const CLEAR_LINE: &str = "\x1b[2K";
pub const CLEAR_LINE_RIGHT: &str = "\x1b[K";
pub const CLEAR_LINE_LEFT: &str = "\x1b[1K";
pub const CURSOR_HIDE: &str = "\x1b[?25l";
pub const CURSOR_SHOW: &str = "\x1b[?25h";
pub const CURSOR_SAVE: &str = "\x1b7";
pub const CURSOR_RESTORE: &str = "\x1b8";
pub const CURSOR_HOME: &str = "\x1b[H";
pub const CURSOR_COLOR_RESET: &str = "\x1b]112\x07";
#[must_use]
pub fn cursor_color(r: u8, g: u8, b: u8) -> String {
format!("\x1b]12;#{r:02x}{g:02x}{b:02x}\x07")
}
pub const ALT_SCREEN_ON: &str = "\x1b[?1049h";
pub const ALT_SCREEN_OFF: &str = "\x1b[?1049l";
pub const MOUSE_ON: &str = "\x1b[?1003h\x1b[?1006h";
pub const MOUSE_OFF: &str = "\x1b[?1003l\x1b[?1006l";
pub const BRACKETED_PASTE_ON: &str = "\x1b[?2004h";
pub const BRACKETED_PASTE_OFF: &str = "\x1b[?2004l";
pub const FOCUS_ON: &str = "\x1b[?1004h";
pub const FOCUS_OFF: &str = "\x1b[?1004l";
pub const REQUEST_SIZE: &str = "\x1b[18t";
pub mod query {
pub const DEVICE_ATTRIBUTES: &str = "\x1b[c";
pub const DEVICE_ATTRIBUTES_SECONDARY: &str = "\x1b[>c";
pub const XTVERSION: &str = "\x1b[>0q";
pub const PIXEL_RESOLUTION: &str = "\x1b[14t";
pub const KITTY_KEYBOARD: &str = "\x1b[?u";
}
pub const TITLE_PREFIX: &str = "\x1b]0;";
pub const TITLE_SUFFIX: &str = "\x1b\\";
pub const SOFT_RESET: &str = "\x1bc";
pub mod cursor_style {
pub const BLOCK_BLINK: &str = "\x1b[1 q";
pub const BLOCK_STEADY: &str = "\x1b[2 q";
pub const UNDERLINE_BLINK: &str = "\x1b[3 q";
pub const UNDERLINE_STEADY: &str = "\x1b[4 q";
pub const BAR_BLINK: &str = "\x1b[5 q";
pub const BAR_STEADY: &str = "\x1b[6 q";
pub const DEFAULT: &str = "\x1b[0 q";
}
pub mod sync {
pub const BEGIN: &str = "\x1b[?2026h";
pub const END: &str = "\x1b[?2026l";
}
pub mod color {
pub const FG_DEFAULT: &str = "\x1b[39m";
pub const BG_DEFAULT: &str = "\x1b[49m";
}
pub mod attr {
pub const RESET_INTENSITY: &str = "\x1b[22m";
pub const RESET_ITALIC: &str = "\x1b[23m";
pub const RESET_UNDERLINE: &str = "\x1b[24m";
pub const RESET_BLINK: &str = "\x1b[25m";
pub const RESET_INVERSE: &str = "\x1b[27m";
pub const RESET_HIDDEN: &str = "\x1b[28m";
pub const RESET_STRIKETHROUGH: &str = "\x1b[29m";
}
#[cfg(test)]
mod tests {
use super::*;
fn starts_with_csi(s: &str) -> bool {
s.starts_with("\x1b[")
}
fn starts_with_osc(s: &str) -> bool {
s.starts_with("\x1b]")
}
#[test]
fn test_reset_sgr0() {
assert_eq!(RESET, "\x1b[0m");
assert!(starts_with_csi(RESET));
assert!(RESET.ends_with('m'));
}
#[test]
fn test_clear_screen_ed2() {
assert_eq!(CLEAR_SCREEN, "\x1b[2J");
assert!(starts_with_csi(CLEAR_SCREEN));
assert!(CLEAR_SCREEN.ends_with('J'));
}
#[test]
fn test_clear_screen_below_ed0() {
assert_eq!(CLEAR_SCREEN_BELOW, "\x1b[J");
assert!(starts_with_csi(CLEAR_SCREEN_BELOW));
}
#[test]
fn test_clear_screen_above_ed1() {
assert_eq!(CLEAR_SCREEN_ABOVE, "\x1b[1J");
assert!(starts_with_csi(CLEAR_SCREEN_ABOVE));
}
#[test]
fn test_clear_line_el2() {
assert_eq!(CLEAR_LINE, "\x1b[2K");
assert!(starts_with_csi(CLEAR_LINE));
assert!(CLEAR_LINE.ends_with('K'));
}
#[test]
fn test_clear_line_right_el0() {
assert_eq!(CLEAR_LINE_RIGHT, "\x1b[K");
assert!(starts_with_csi(CLEAR_LINE_RIGHT));
}
#[test]
fn test_clear_line_left_el1() {
assert_eq!(CLEAR_LINE_LEFT, "\x1b[1K");
assert!(starts_with_csi(CLEAR_LINE_LEFT));
}
#[test]
fn test_cursor_hide_dectcem() {
assert_eq!(CURSOR_HIDE, "\x1b[?25l");
assert!(starts_with_csi(CURSOR_HIDE));
assert!(CURSOR_HIDE.contains("?25"));
assert!(CURSOR_HIDE.ends_with('l'));
}
#[test]
fn test_cursor_show_dectcem() {
assert_eq!(CURSOR_SHOW, "\x1b[?25h");
assert!(starts_with_csi(CURSOR_SHOW));
assert!(CURSOR_SHOW.contains("?25"));
assert!(CURSOR_SHOW.ends_with('h'));
}
#[test]
fn test_cursor_save_decsc() {
assert_eq!(CURSOR_SAVE, "\x1b7");
assert!(CURSOR_SAVE.starts_with('\x1b'));
assert_eq!(CURSOR_SAVE.len(), 2);
}
#[test]
fn test_cursor_restore_decrc() {
assert_eq!(CURSOR_RESTORE, "\x1b8");
assert!(CURSOR_RESTORE.starts_with('\x1b'));
assert_eq!(CURSOR_RESTORE.len(), 2);
}
#[test]
fn test_cursor_home_cup() {
assert_eq!(CURSOR_HOME, "\x1b[H");
assert!(starts_with_csi(CURSOR_HOME));
assert!(CURSOR_HOME.ends_with('H'));
}
#[test]
fn test_cursor_color_reset_osc112() {
assert_eq!(CURSOR_COLOR_RESET, "\x1b]112\x07");
assert!(starts_with_osc(CURSOR_COLOR_RESET));
assert!(CURSOR_COLOR_RESET.ends_with('\x07')); }
#[test]
fn test_cursor_color_function_osc12() {
let result = cursor_color(255, 128, 0);
assert!(starts_with_osc(&result));
assert!(result.contains("12;"));
assert!(result.contains("#ff8000"));
assert!(result.ends_with('\x07'));
let black = cursor_color(0, 0, 0);
assert!(black.contains("#000000"));
let white = cursor_color(255, 255, 255);
assert!(white.contains("#ffffff"));
}
#[test]
fn test_alt_screen_on_dec1049() {
assert_eq!(ALT_SCREEN_ON, "\x1b[?1049h");
assert!(starts_with_csi(ALT_SCREEN_ON));
assert!(ALT_SCREEN_ON.contains("?1049"));
assert!(ALT_SCREEN_ON.ends_with('h'));
}
#[test]
fn test_alt_screen_off_dec1049() {
assert_eq!(ALT_SCREEN_OFF, "\x1b[?1049l");
assert!(starts_with_csi(ALT_SCREEN_OFF));
assert!(ALT_SCREEN_OFF.contains("?1049"));
assert!(ALT_SCREEN_OFF.ends_with('l'));
}
#[test]
fn test_mouse_on_sgr1006() {
assert_eq!(MOUSE_ON, "\x1b[?1003h\x1b[?1006h");
assert!(MOUSE_ON.contains("?1003h")); assert!(MOUSE_ON.contains("?1006h")); }
#[test]
fn test_mouse_off() {
assert_eq!(MOUSE_OFF, "\x1b[?1003l\x1b[?1006l");
assert!(MOUSE_OFF.contains("?1003l"));
assert!(MOUSE_OFF.contains("?1006l"));
}
#[test]
fn test_bracketed_paste_on() {
assert_eq!(BRACKETED_PASTE_ON, "\x1b[?2004h");
assert!(starts_with_csi(BRACKETED_PASTE_ON));
assert!(BRACKETED_PASTE_ON.contains("?2004"));
}
#[test]
fn test_bracketed_paste_off() {
assert_eq!(BRACKETED_PASTE_OFF, "\x1b[?2004l");
assert!(starts_with_csi(BRACKETED_PASTE_OFF));
}
#[test]
fn test_focus_on() {
assert_eq!(FOCUS_ON, "\x1b[?1004h");
assert!(starts_with_csi(FOCUS_ON));
assert!(FOCUS_ON.contains("?1004"));
}
#[test]
fn test_focus_off() {
assert_eq!(FOCUS_OFF, "\x1b[?1004l");
assert!(starts_with_csi(FOCUS_OFF));
}
#[test]
fn test_request_size_xtwinops() {
assert_eq!(REQUEST_SIZE, "\x1b[18t");
assert!(starts_with_csi(REQUEST_SIZE));
assert!(REQUEST_SIZE.ends_with('t'));
}
#[test]
fn test_query_device_attributes_da1() {
assert_eq!(query::DEVICE_ATTRIBUTES, "\x1b[c");
assert!(starts_with_csi(query::DEVICE_ATTRIBUTES));
assert!(query::DEVICE_ATTRIBUTES.ends_with('c'));
}
#[test]
fn test_query_device_attributes_secondary_da2() {
assert_eq!(query::DEVICE_ATTRIBUTES_SECONDARY, "\x1b[>c");
assert!(starts_with_csi(query::DEVICE_ATTRIBUTES_SECONDARY));
assert!(query::DEVICE_ATTRIBUTES_SECONDARY.contains('>'));
}
#[test]
fn test_query_xtversion() {
assert_eq!(query::XTVERSION, "\x1b[>0q");
assert!(starts_with_csi(query::XTVERSION));
assert!(query::XTVERSION.ends_with('q'));
}
#[test]
fn test_query_pixel_resolution() {
assert_eq!(query::PIXEL_RESOLUTION, "\x1b[14t");
assert!(starts_with_csi(query::PIXEL_RESOLUTION));
assert!(query::PIXEL_RESOLUTION.ends_with('t'));
}
#[test]
fn test_query_kitty_keyboard() {
assert_eq!(query::KITTY_KEYBOARD, "\x1b[?u");
assert!(starts_with_csi(query::KITTY_KEYBOARD));
assert!(query::KITTY_KEYBOARD.ends_with('u'));
}
#[test]
fn test_title_prefix_osc0() {
assert_eq!(TITLE_PREFIX, "\x1b]0;");
assert!(starts_with_osc(TITLE_PREFIX));
assert!(TITLE_PREFIX.contains("0;"));
}
#[test]
fn test_title_suffix_st() {
assert_eq!(TITLE_SUFFIX, "\x1b\\");
assert!(TITLE_SUFFIX.starts_with('\x1b'));
assert_eq!(TITLE_SUFFIX.len(), 2);
}
#[test]
fn test_title_sequence_complete() {
let title = "Test Title";
let full = format!("{TITLE_PREFIX}{title}{TITLE_SUFFIX}");
assert!(full.starts_with("\x1b]0;"));
assert!(full.contains("Test Title"));
assert!(full.ends_with("\x1b\\"));
}
#[test]
fn test_soft_reset_ris() {
assert_eq!(SOFT_RESET, "\x1bc");
assert!(SOFT_RESET.starts_with('\x1b'));
assert_eq!(SOFT_RESET.len(), 2);
}
#[test]
fn test_cursor_style_block_blink() {
assert_eq!(cursor_style::BLOCK_BLINK, "\x1b[1 q");
assert!(starts_with_csi(cursor_style::BLOCK_BLINK));
assert!(cursor_style::BLOCK_BLINK.ends_with(" q"));
}
#[test]
fn test_cursor_style_block_steady() {
assert_eq!(cursor_style::BLOCK_STEADY, "\x1b[2 q");
assert!(cursor_style::BLOCK_STEADY.contains('2'));
}
#[test]
fn test_cursor_style_underline_blink() {
assert_eq!(cursor_style::UNDERLINE_BLINK, "\x1b[3 q");
assert!(cursor_style::UNDERLINE_BLINK.contains('3'));
}
#[test]
fn test_cursor_style_underline_steady() {
assert_eq!(cursor_style::UNDERLINE_STEADY, "\x1b[4 q");
assert!(cursor_style::UNDERLINE_STEADY.contains('4'));
}
#[test]
fn test_cursor_style_bar_blink() {
assert_eq!(cursor_style::BAR_BLINK, "\x1b[5 q");
assert!(cursor_style::BAR_BLINK.contains('5'));
}
#[test]
fn test_cursor_style_bar_steady() {
assert_eq!(cursor_style::BAR_STEADY, "\x1b[6 q");
assert!(cursor_style::BAR_STEADY.contains('6'));
}
#[test]
fn test_cursor_style_default() {
assert_eq!(cursor_style::DEFAULT, "\x1b[0 q");
assert!(cursor_style::DEFAULT.contains('0'));
}
#[test]
fn test_sync_begin() {
assert_eq!(sync::BEGIN, "\x1b[?2026h");
assert!(starts_with_csi(sync::BEGIN));
assert!(sync::BEGIN.contains("?2026"));
assert!(sync::BEGIN.ends_with('h'));
}
#[test]
fn test_sync_end() {
assert_eq!(sync::END, "\x1b[?2026l");
assert!(starts_with_csi(sync::END));
assert!(sync::END.contains("?2026"));
assert!(sync::END.ends_with('l'));
}
#[test]
fn test_color_fg_default_sgr39() {
assert_eq!(color::FG_DEFAULT, "\x1b[39m");
assert!(starts_with_csi(color::FG_DEFAULT));
assert!(color::FG_DEFAULT.ends_with('m'));
}
#[test]
fn test_color_bg_default_sgr49() {
assert_eq!(color::BG_DEFAULT, "\x1b[49m");
assert!(starts_with_csi(color::BG_DEFAULT));
assert!(color::BG_DEFAULT.ends_with('m'));
}
#[test]
fn test_attr_reset_intensity_sgr22() {
assert_eq!(attr::RESET_INTENSITY, "\x1b[22m");
assert!(starts_with_csi(attr::RESET_INTENSITY));
}
#[test]
fn test_attr_reset_italic_sgr23() {
assert_eq!(attr::RESET_ITALIC, "\x1b[23m");
assert!(starts_with_csi(attr::RESET_ITALIC));
}
#[test]
fn test_attr_reset_underline_sgr24() {
assert_eq!(attr::RESET_UNDERLINE, "\x1b[24m");
assert!(starts_with_csi(attr::RESET_UNDERLINE));
}
#[test]
fn test_attr_reset_blink_sgr25() {
assert_eq!(attr::RESET_BLINK, "\x1b[25m");
assert!(starts_with_csi(attr::RESET_BLINK));
}
#[test]
fn test_attr_reset_inverse_sgr27() {
assert_eq!(attr::RESET_INVERSE, "\x1b[27m");
assert!(starts_with_csi(attr::RESET_INVERSE));
}
#[test]
fn test_attr_reset_hidden_sgr28() {
assert_eq!(attr::RESET_HIDDEN, "\x1b[28m");
assert!(starts_with_csi(attr::RESET_HIDDEN));
}
#[test]
fn test_attr_reset_strikethrough_sgr29() {
assert_eq!(attr::RESET_STRIKETHROUGH, "\x1b[29m");
assert!(starts_with_csi(attr::RESET_STRIKETHROUGH));
}
#[test]
fn test_all_csi_sequences_have_terminator() {
let csi_sequences = [
RESET,
CLEAR_SCREEN,
CLEAR_SCREEN_BELOW,
CLEAR_SCREEN_ABOVE,
CLEAR_LINE,
CLEAR_LINE_RIGHT,
CLEAR_LINE_LEFT,
CURSOR_HIDE,
CURSOR_SHOW,
CURSOR_HOME,
ALT_SCREEN_ON,
ALT_SCREEN_OFF,
BRACKETED_PASTE_ON,
BRACKETED_PASTE_OFF,
FOCUS_ON,
FOCUS_OFF,
REQUEST_SIZE,
];
for seq in &csi_sequences {
assert!(
starts_with_csi(seq),
"Sequence {seq:?} should start with CSI"
);
let last_char = seq.chars().last().unwrap();
assert!(
last_char.is_ascii_alphabetic(),
"Sequence {seq:?} should end with letter, got {last_char:?}"
);
}
}
#[test]
fn test_all_sequences_are_valid_utf8() {
let all_sequences: &[&str] = &[
RESET,
CLEAR_SCREEN,
CLEAR_SCREEN_BELOW,
CLEAR_SCREEN_ABOVE,
CLEAR_LINE,
CLEAR_LINE_RIGHT,
CLEAR_LINE_LEFT,
CURSOR_HIDE,
CURSOR_SHOW,
CURSOR_SAVE,
CURSOR_RESTORE,
CURSOR_HOME,
CURSOR_COLOR_RESET,
ALT_SCREEN_ON,
ALT_SCREEN_OFF,
MOUSE_ON,
MOUSE_OFF,
BRACKETED_PASTE_ON,
BRACKETED_PASTE_OFF,
FOCUS_ON,
FOCUS_OFF,
REQUEST_SIZE,
TITLE_PREFIX,
TITLE_SUFFIX,
SOFT_RESET,
query::DEVICE_ATTRIBUTES,
query::DEVICE_ATTRIBUTES_SECONDARY,
query::XTVERSION,
query::PIXEL_RESOLUTION,
query::KITTY_KEYBOARD,
cursor_style::BLOCK_BLINK,
cursor_style::BLOCK_STEADY,
cursor_style::UNDERLINE_BLINK,
cursor_style::UNDERLINE_STEADY,
cursor_style::BAR_BLINK,
cursor_style::BAR_STEADY,
cursor_style::DEFAULT,
sync::BEGIN,
sync::END,
color::FG_DEFAULT,
color::BG_DEFAULT,
attr::RESET_INTENSITY,
attr::RESET_ITALIC,
attr::RESET_UNDERLINE,
attr::RESET_BLINK,
attr::RESET_INVERSE,
attr::RESET_HIDDEN,
attr::RESET_STRIKETHROUGH,
];
for seq in all_sequences {
assert!(!seq.is_empty(), "Sequence should not be empty");
assert!(
seq.contains('\x1b'),
"Sequence {seq:?} should contain ESC character"
);
}
}
#[test]
fn test_set_reset_pairs() {
let pairs = [
(CURSOR_HIDE, CURSOR_SHOW),
(ALT_SCREEN_ON, ALT_SCREEN_OFF),
(BRACKETED_PASTE_ON, BRACKETED_PASTE_OFF),
(FOCUS_ON, FOCUS_OFF),
(sync::BEGIN, sync::END),
];
for (set_seq, reset_seq) in &pairs {
let set_last = set_seq.chars().last().unwrap();
let reset_last = reset_seq.chars().last().unwrap();
assert!(
set_last == 'h' || set_last == 'l',
"SET sequence should end with h or l"
);
assert!(
reset_last == 'h' || reset_last == 'l',
"RESET sequence should end with h or l"
);
assert_ne!(
set_last, reset_last,
"SET and RESET should have opposite terminators"
);
}
}
}