1use std::fmt::{Display, Formatter, Write};
2
3#[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 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#[derive(Copy, Clone, Eq, Ord, PartialOrd, PartialEq, Hash, Debug)]
77pub enum AnsiSeq {
78 Move {
80 up: u16,
82 down: u16,
84 left: u16,
86 right: u16,
88 },
89 MoveLines {
91 up: u16,
93 down: u16,
95 },
96 ResetStyle,
98 Underline,
100 ClearCursorToEndOfLine,
102 ClearCursorToBeginningOfLine,
104 ClearLine,
106 ClearCursorToEndOfScreen,
108 ClearCursorToBeginningOfScreen,
110 ClearAllScreen,
112 ShowAndHideCursor {
114 show: bool,
116 },
117 AbsoluteMove {
119 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}