Skip to main content

tty_overwriter/
ansi_seq.rs

1use std::fmt::{Display, Formatter, Write};
2
3/// A shorthand builder for `AnsiSeq::Move`
4/// ```no_run
5/// use tty_overwriter::prelude::Movement;
6/// let movement = Movement::new().down(3).left(2);
7/// ```
8#[derive(Default, Copy, Clone, Eq, Ord, PartialOrd, PartialEq, Hash, Debug)]
9pub struct Movement {
10    up: u16,
11    down: u16,
12    left: u16,
13    right: u16,
14}
15
16impl From<&Movement> for AnsiSeq {
17    fn from(value: &Movement) -> Self {
18        Self::Move {
19            up: value.up,
20            down: value.down,
21            left: value.left,
22            right: value.right,
23        }
24    }
25}
26
27impl Display for Movement {
28    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
29        <&Movement as Into<AnsiSeq>>::into(self).fmt(f)
30    }
31}
32
33impl Movement {
34    /// empty constructor
35    pub fn new() -> Self {
36        Default::default()
37    }
38
39    #[allow(missing_docs)]
40    #[inline]
41    pub fn up(self, up: u16) -> Self {
42        Self { up, ..self }
43    }
44
45    #[allow(missing_docs)]
46    #[inline]
47    pub fn down(self, down: u16) -> Self {
48        Self { down, ..self }
49    }
50
51    #[allow(missing_docs)]
52    #[inline]
53    pub fn right(self, right: u16) -> Self {
54        Self { right, ..self }
55    }
56
57    #[allow(missing_docs)]
58    #[inline]
59    pub fn left(self, left: u16) -> Self {
60        Self { left, ..self }
61    }
62}
63
64/// A list of Ansi sequences.
65/// Thought to be written to `std::io::Write`.
66/// ```
67/// use tty_overwriter::prelude::AnsiSeq;
68/// print!("Hello, word!");
69/// // we did a spelling mistake
70/// let left = AnsiSeq::AbsoluteMove {horizontal: 0};
71/// println!("{left}Hello, world!")
72/// ```
73/// Codes provided by [Hand Wiki](https://handwiki.org/wiki/ANSI_escape_code#Colors)
74/// For ease of debugging, if cfg(test) then `AnsiSeq::fmt` will actually
75/// display the code.
76#[derive(Copy, Clone, Eq, Ord, PartialOrd, PartialEq, Hash, Debug)]
77pub enum AnsiSeq {
78    /// Move in straight line. Applied in order:  Up, Left, Down, Right.
79    Move {
80        /// Move n spaces right
81        up: u16,
82        /// Move n spaces down
83        down: u16,
84        /// Move n spaces left
85        left: u16,
86        /// Move n spaces right
87        right: u16,
88    },
89    /// Move vertically to the beginning of lines. Applied in order: Up, Down.
90    MoveLines {
91        /// Move n lines up
92        up: u16,
93        /// Move n lines down
94        down: u16,
95    },
96    /// toggle OFF all styles
97    ResetStyle,
98    /// toggle ON the Underline style
99    Underline,
100    /// clear the part of the line on the right of the cursor
101    ClearCursorToEndOfLine,
102    /// clear the part of the line on the left of the cursor
103    ClearCursorToBeginningOfLine,
104    /// Clear the line of the terminal on which is the cursor
105    ClearLine,
106    /// Clear the terminal under the cursor
107    ClearCursorToEndOfScreen,
108    /// Clear the terminal over the cursor
109    ClearCursorToBeginningOfScreen,
110    /// Clear the entirety of the terminal
111    ClearAllScreen,
112    /// Show and hide mouse cursor
113    ShowAndHideCursor {
114        /// should or shouldn't show the cursor
115        show: bool,
116    },
117    /// Set the cursor horizontally
118    AbsoluteMove {
119        /// horizontal position of the cursor
120        horizontal: u16,
121    },
122}
123
124fn encode(f: &mut Formatter<'_>, code: &str) -> std::fmt::Result {
125    #[cfg(not(test))]
126    f.write_char(27 as char)?;
127    #[cfg(test)]
128    f.write_str("\\e")?;
129    f.write_char('[')?;
130    f.write_str(code)?;
131    Ok(())
132}
133
134impl Display for AnsiSeq {
135    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
136        match self {
137            Self::Move {
138                up,
139                down,
140                left,
141                right,
142            } => {
143                if 0 < *up {
144                    encode(f, &format!("{up}A"))?;
145                }
146                if 0 < *left {
147                    encode(f, &format!("{left}D"))?;
148                }
149                if 0 < *down {
150                    encode(f, &format!("{down}B"))?;
151                }
152                if 0 < *right {
153                    encode(f, &format!("{right}C"))?;
154                }
155            }
156            Self::MoveLines { up, down } => {
157                if 0 < *up {
158                    encode(f, &format!("{up}F"))?;
159                }
160                if 0 < *down {
161                    encode(f, &format!("{down}E"))?;
162                }
163            }
164            Self::ResetStyle => encode(f, "0m")?,
165            Self::Underline => encode(f, "4m")?,
166            Self::ClearCursorToEndOfLine => encode(f, "0K")?,
167            Self::ClearCursorToBeginningOfLine => encode(f, "1K")?,
168            Self::ClearLine => encode(f, "2K")?,
169            Self::ClearAllScreen => encode(f, "2J")?,
170            Self::ClearCursorToBeginningOfScreen => encode(f, "1J")?,
171            Self::ClearCursorToEndOfScreen => encode(f, "0J")?,
172            Self::ShowAndHideCursor { show } => encode(f, if *show { "?25h" } else { "?25l" })?,
173            Self::AbsoluteMove { horizontal } => encode(f, &format!("{horizontal}G"))?,
174        }
175
176        Ok(())
177    }
178}
179
180#[cfg(test)]
181mod tests {
182    use super::*;
183
184    #[test]
185    fn test_move_up() {
186        let seq = AnsiSeq::Move {
187            up: 3,
188            down: 0,
189            left: 0,
190            right: 0,
191        };
192
193        assert_eq!(&format!("{seq}"), "\\e[3A")
194    }
195}