use eyre::Result;
pub mod prelude {
pub use crate::{
Ansi, Colour, CursorStyle, CursorVisibility, DisplayEraseMode, LineEraseMode, SgrParameter,
};
}
#[macro_export]
macro_rules! ansi {
($( $l:expr ),*) => { concat!("\x1B[", $( $l ),*) };
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum Ansi {
CursorPosition(u64, u64),
CursorStyle(CursorStyle),
CursorVisibility(CursorVisibility),
CursorUp(u64),
CursorDown(u64),
CursorLeft(u64),
CursorRight(u64),
CursorNextLine(u64),
CursorPreviousLine(u64),
CursorHorizontalAbsolute(u64),
SaveCursorPosition,
RestoreCursorPosition,
EraseInDisplay(DisplayEraseMode),
EraseInLine(LineEraseMode),
ScrollUp(u64),
ScrollDown(u64),
TerminalSize(u64, u64),
TerminalTitle(String),
TerminalForegroundColour(Colour),
TerminalBackgroundColour(Colour),
Sgr(Vec<SgrParameter>),
}
impl Ansi {
pub fn render(&self, f: &mut impl std::fmt::Write) -> Result<()> {
match self {
Self::CursorPosition(x, y) => {
write!(f, ansi!("{};{}H"), y + 1, x + 1)
}
Self::CursorStyle(style) => match style {
CursorStyle::Block => {
write!(f, ansi!("2 q"))
}
CursorStyle::Bar => {
write!(f, ansi!("5 q"))
}
CursorStyle::HollowBlock => {
write!(f, ansi!("2 q"))
}
},
Self::CursorVisibility(visibility) => match visibility {
CursorVisibility::Visible => {
write!(f, ansi!("?25h"))
}
CursorVisibility::Invisible => {
write!(f, ansi!("?25l"))
}
},
Self::CursorUp(count) => {
write!(f, ansi!("{}A"), count)
}
Self::CursorDown(count) => {
write!(f, ansi!("{}B"), count)
}
Self::CursorLeft(count) => {
write!(f, ansi!("{}D"), count)
}
Self::CursorRight(count) => {
write!(f, ansi!("{}C"), count)
}
Self::CursorNextLine(count) => {
write!(f, ansi!("{}E"), count)
}
Self::CursorPreviousLine(count) => {
write!(f, ansi!("{}F"), count)
}
Self::CursorHorizontalAbsolute(x) => {
write!(f, ansi!("{}G"), x + 1)
}
Self::SaveCursorPosition => {
write!(f, ansi!("s"))
}
Self::RestoreCursorPosition => {
write!(f, ansi!("u"))
}
Self::EraseInDisplay(mode) => match mode {
DisplayEraseMode::All => {
write!(f, ansi!("2J"))
}
DisplayEraseMode::FromCursorToEnd => {
write!(f, ansi!("0J"))
}
DisplayEraseMode::FromCursorToStart => {
write!(f, ansi!("1J"))
}
DisplayEraseMode::ScrollbackBuffer => {
write!(f, ansi!("3J"))
}
},
Self::EraseInLine(mode) => match mode {
LineEraseMode::All => {
write!(f, ansi!("2K"))
}
LineEraseMode::FromCursorToEnd => {
write!(f, ansi!("0K"))
}
LineEraseMode::FromCursorToStart => {
write!(f, ansi!("1K"))
}
},
Self::ScrollUp(count) => {
write!(f, ansi!("{}S"), count)
}
Self::ScrollDown(count) => {
write!(f, ansi!("{}T"), count)
}
Self::TerminalSize(width, height) => {
write!(f, ansi!("8;{};{}t"), height, width)
}
Self::TerminalTitle(title) => {
write!(f, "\x1B]0;{title}\x07")
}
Self::TerminalForegroundColour(colour) => {
write!(f, ansi!("38;5;{}"), colour.index())
}
Self::TerminalBackgroundColour(colour) => {
write!(f, ansi!("48;5;{}"), colour.index())
}
Self::Sgr(attributes) => {
let mut first = true;
write!(f, ansi!(""))?;
for attribute in attributes {
if first {
first = false;
} else {
write!(f, ";")?;
}
match attribute {
SgrParameter::Reset => {
write!(f, "0")
}
SgrParameter::Bold => {
write!(f, "1")
}
SgrParameter::Faint => {
write!(f, "2")
}
SgrParameter::Italic => {
write!(f, "3")
}
SgrParameter::Underline => {
write!(f, "4")
}
SgrParameter::Blink => {
write!(f, "5")
}
SgrParameter::RapidBlink => {
write!(f, "6")
}
SgrParameter::ReverseVideo => {
write!(f, "7")
}
SgrParameter::Conceal => {
write!(f, "8")
}
SgrParameter::CrossedOut => {
write!(f, "9")
}
SgrParameter::PrimaryFont => {
write!(f, "10")
}
SgrParameter::AlternativeFont(idx) => {
write!(f, "{}", 10 + idx)
}
SgrParameter::Fraktur => {
write!(f, "20")
}
SgrParameter::DoubleUnderline => {
write!(f, "21")
}
SgrParameter::NormalIntensity => {
write!(f, "22")
}
SgrParameter::NotItalicOrBlackletter => {
write!(f, "23")
}
SgrParameter::NotUnderlined => {
write!(f, "24")
}
SgrParameter::SteadyCursor => {
write!(f, "25")
}
SgrParameter::ProportionalSpacing => {
write!(f, "26")
}
SgrParameter::NotReversed => {
write!(f, "27")
}
SgrParameter::Reveal => {
write!(f, "28")
}
SgrParameter::NotCrossedOut => {
write!(f, "29")
}
SgrParameter::Framed => {
write!(f, "51")
}
SgrParameter::Encircled => {
write!(f, "52")
}
SgrParameter::Overlined => {
write!(f, "53")
}
SgrParameter::NotFramedOrEncircled => {
write!(f, "54")
}
SgrParameter::NotOverlined => {
write!(f, "55")
}
SgrParameter::IdeogramUnderlineOrRightSideLine => {
write!(f, "60")
}
SgrParameter::IdeogramDoubleUnderlineOrDoubleLineOnTheRightSide => {
write!(f, "61")
}
SgrParameter::IdeogramOverlineOrLeftSideLine => {
write!(f, "62")
}
SgrParameter::IdeogramDoubleOverlineOrDoubleLineOnTheLeftSide => {
write!(f, "63")
}
SgrParameter::IdeogramStressMarking => {
write!(f, "64")
}
SgrParameter::IdeogramAttributesOff => {
write!(f, "65")
}
SgrParameter::ForegroundColour(colour) => {
write!(f, "38;5;{}", colour.index())
}
SgrParameter::BackgroundColour(colour) => {
write!(f, "48;5;{}", colour.index())
}
SgrParameter::HexForegroundColour(hex) => {
let (r, g, b) = Self::rgb(hex);
write!(f, "38;2;{r};{g};{b}")
}
SgrParameter::HexBackgroundColour(hex) => {
let (r, g, b) = Self::rgb(hex);
write!(f, "48;2;{r};{g};{b}")
}
SgrParameter::DefaultForegroundColour => {
write!(f, "39")
}
SgrParameter::DefaultBackgroundColour => {
write!(f, "49")
}
SgrParameter::DisableProportionalSpacing => {
write!(f, "50")
}
SgrParameter::UnderlineColour(colour) => {
write!(f, "58;5;{}", colour.index())
}
SgrParameter::HexUnderlineColour(hex) => {
let (r, g, b) = Self::rgb(hex);
write!(f, "58;2;{r};{g};{b}")
}
SgrParameter::DefaultUnderlineColour => {
write!(f, "59")
}
SgrParameter::Superscript => {
write!(f, "73")
}
SgrParameter::Subscript => {
write!(f, "74")
}
SgrParameter::NotSuperscriptOrSubscript => {
write!(f, "75")
}
}?;
}
write!(f, "m")
}
}
.map_err(|e| e.into())
}
fn rgb(hex: &u32) -> (u32, u32, u32) {
let r = (hex >> 16) & 0xFF;
let g = (hex >> 8) & 0xFF;
let b = hex & 0xFF;
(r, g, b)
}
}
impl std::fmt::Display for Ansi {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.render(f).map_err(|_| std::fmt::Error)
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum CursorStyle {
Block,
Bar,
HollowBlock,
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum CursorVisibility {
Visible,
Invisible,
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum Colour {
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
BrightBlack,
BrightRed,
BrightGreen,
BrightYellow,
BrightBlue,
BrightMagenta,
BrightCyan,
BrightWhite,
}
impl Colour {
pub fn index(&self) -> u64 {
*self as u64
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum DisplayEraseMode {
FromCursorToEnd,
FromCursorToStart,
All,
ScrollbackBuffer,
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum LineEraseMode {
FromCursorToEnd,
FromCursorToStart,
All,
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum SgrParameter {
Reset,
Bold,
Faint,
Italic,
Underline,
Blink,
RapidBlink,
ReverseVideo,
Conceal,
CrossedOut,
PrimaryFont,
AlternativeFont(u64),
Fraktur,
DoubleUnderline,
NormalIntensity,
NotItalicOrBlackletter,
NotUnderlined,
SteadyCursor,
ProportionalSpacing,
NotReversed,
Reveal,
NotCrossedOut,
ForegroundColour(Colour),
BackgroundColour(Colour),
HexForegroundColour(u32),
HexBackgroundColour(u32),
DefaultForegroundColour,
DefaultBackgroundColour,
DisableProportionalSpacing,
Framed,
Encircled,
Overlined,
NotFramedOrEncircled,
NotOverlined,
UnderlineColour(Colour),
HexUnderlineColour(u32),
DefaultUnderlineColour,
IdeogramUnderlineOrRightSideLine,
IdeogramDoubleUnderlineOrDoubleLineOnTheRightSide,
IdeogramOverlineOrLeftSideLine,
IdeogramDoubleOverlineOrDoubleLineOnTheLeftSide,
IdeogramStressMarking,
IdeogramAttributesOff,
Superscript,
Subscript,
NotSuperscriptOrSubscript,
}
#[cfg(test)]
mod tests {
use eyre::Result;
use super::{Ansi, DisplayEraseMode, SgrParameter};
#[test]
fn test_works_as_expected() -> Result<()> {
let mut buffer = String::new();
Ansi::CursorPosition(0, 0).render(&mut buffer)?;
assert_eq!("\u{1b}[1;1H", buffer);
buffer.clear();
Ansi::CursorDown(1).render(&mut buffer)?;
assert_eq!("\u{1b}[1B", buffer);
buffer.clear();
Ansi::CursorUp(1).render(&mut buffer)?;
assert_eq!("\u{1b}[1A", buffer);
buffer.clear();
Ansi::CursorLeft(1).render(&mut buffer)?;
assert_eq!("\u{1b}[1D", buffer);
buffer.clear();
Ansi::CursorRight(1).render(&mut buffer)?;
assert_eq!("\u{1b}[1C", buffer);
buffer.clear();
Ansi::CursorNextLine(1).render(&mut buffer)?;
assert_eq!("\u{1b}[1E", buffer);
buffer.clear();
Ansi::CursorPreviousLine(1).render(&mut buffer)?;
assert_eq!("\u{1b}[1F", buffer);
buffer.clear();
Ansi::CursorHorizontalAbsolute(1).render(&mut buffer)?;
assert_eq!("\u{1b}[2G", buffer);
buffer.clear();
Ansi::CursorPosition(1, 1).render(&mut buffer)?;
assert_eq!("\u{1b}[2;2H", buffer);
buffer.clear();
Ansi::EraseInDisplay(DisplayEraseMode::All).render(&mut buffer)?;
assert_eq!("\u{1b}[2J", buffer);
buffer.clear();
Ansi::Sgr(vec![SgrParameter::HexForegroundColour(0xDB325C)]).render(&mut buffer)?;
assert_eq!("\u{1b}[38;2;219;50;92m", buffer);
buffer.clear();
Ansi::Sgr(vec![SgrParameter::HexBackgroundColour(0xDB325C)]).render(&mut buffer)?;
assert_eq!("\u{1b}[48;2;219;50;92m", buffer);
buffer.clear();
Ok(())
}
}