use crate::console::{Console, ConsoleOptions};
use crate::protocol::Renderable;
use crate::segment::Segment;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ControlType {
Bell,
CarriageReturn,
Home,
Clear,
ShowCursor,
HideCursor,
EnableAltScreen,
DisableAltScreen,
CursorUp(u32),
CursorDown(u32),
CursorForward(u32),
CursorBackward(u32),
CursorMoveToColumn(u32),
CursorMoveTo(u32, u32),
EraseInLine(u32),
}
impl ControlType {
fn format(self) -> String {
match self {
ControlType::Bell => "\x07".to_string(),
ControlType::CarriageReturn => "\r".to_string(),
ControlType::Home => "\x1b[H".to_string(),
ControlType::Clear => "\x1b[2J".to_string(),
ControlType::EnableAltScreen => "\x1b[?1049h".to_string(),
ControlType::DisableAltScreen => "\x1b[?1049l".to_string(),
ControlType::ShowCursor => "\x1b[?25h".to_string(),
ControlType::HideCursor => "\x1b[?25l".to_string(),
ControlType::CursorUp(n) => format!("\x1b[{n}A"),
ControlType::CursorDown(n) => format!("\x1b[{n}B"),
ControlType::CursorForward(n) => format!("\x1b[{n}C"),
ControlType::CursorBackward(n) => format!("\x1b[{n}D"),
ControlType::CursorMoveToColumn(x) => format!("\x1b[{}G", x + 1),
ControlType::CursorMoveTo(x, y) => format!("\x1b[{};{}H", y + 1, x + 1),
ControlType::EraseInLine(n) => format!("\x1b[{n}K"),
}
}
}
pub struct Control {
segment: Segment,
}
impl Control {
pub fn new(codes: &[ControlType]) -> Self {
let text: String = codes.iter().map(|c| c.format()).collect();
Control {
segment: Segment::control(text),
}
}
fn single(code: ControlType) -> Self {
Control::new(&[code])
}
pub fn bell() -> Self {
Control::single(ControlType::Bell)
}
pub fn home() -> Self {
Control::single(ControlType::Home)
}
pub fn clear() -> Self {
Control::single(ControlType::Clear)
}
#[allow(clippy::should_implement_trait)]
pub fn move_(x: i32, y: i32) -> Self {
let mut codes = Vec::new();
if x != 0 {
codes.push(if x > 0 {
ControlType::CursorForward(x.unsigned_abs())
} else {
ControlType::CursorBackward(x.unsigned_abs())
});
}
if y != 0 {
codes.push(if y > 0 {
ControlType::CursorDown(y.unsigned_abs())
} else {
ControlType::CursorUp(y.unsigned_abs())
});
}
Control::new(&codes)
}
pub fn move_to_column(x: u32, y: i32) -> Self {
if y != 0 {
let vertical = if y > 0 {
ControlType::CursorDown(y.unsigned_abs())
} else {
ControlType::CursorUp(y.unsigned_abs())
};
Control::new(&[ControlType::CursorMoveToColumn(x), vertical])
} else {
Control::single(ControlType::CursorMoveToColumn(x))
}
}
pub fn move_to(x: u32, y: u32) -> Self {
Control::single(ControlType::CursorMoveTo(x, y))
}
pub fn show_cursor(show: bool) -> Self {
Control::single(if show {
ControlType::ShowCursor
} else {
ControlType::HideCursor
})
}
pub fn alt_screen(enable: bool) -> Self {
Control::single(if enable {
ControlType::EnableAltScreen
} else {
ControlType::DisableAltScreen
})
}
pub fn as_str(&self) -> &str {
&self.segment.text
}
}
impl Renderable for Control {
fn rich_render(&self, _console: &Console, _options: &ConsoleOptions) -> Vec<Segment> {
if self.segment.text.is_empty() {
Vec::new()
} else {
vec![self.segment.clone()]
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn escape_strings_match_upstream() {
assert_eq!(Control::clear().as_str(), "\x1b[2J");
assert_eq!(Control::home().as_str(), "\x1b[H");
assert_eq!(Control::bell().as_str(), "\x07");
assert_eq!(Control::show_cursor(true).as_str(), "\x1b[?25h");
assert_eq!(Control::show_cursor(false).as_str(), "\x1b[?25l");
assert_eq!(Control::move_(2, -1).as_str(), "\x1b[2C\x1b[1A");
assert_eq!(Control::move_to(3, 4).as_str(), "\x1b[5;4H");
assert_eq!(Control::move_to_column(5, 0).as_str(), "\x1b[6G");
assert_eq!(Control::alt_screen(true).as_str(), "\x1b[?1049h");
assert_eq!(Control::alt_screen(false).as_str(), "\x1b[?1049l");
}
#[test]
fn renders_control_segment() {
let control = Control::clear();
let segments = control.rich_render(&Console::new(), &Console::new().options());
assert_eq!(segments.len(), 1);
assert!(segments[0].control);
assert_eq!(segments[0].cell_length(), 0);
}
}