Skip to main content

tty_overwriter/
body.rs

1use crate::prelude::AnsiSeq;
2use std::io::Write;
3
4#[derive(Debug, Default)]
5/// A struct to call `overwrite` on, to avoid flicker in terminal.
6pub struct Body {
7    buffer: Vec<usize>,
8}
9
10const CLEAR_TIL_EOL: AnsiSeq = AnsiSeq::ClearCursorToEndOfLine;
11const CLEAR_TIL_EOF: AnsiSeq = AnsiSeq::ClearCursorToEndOfScreen;
12const JUMP_AT_BEGINNING: AnsiSeq = AnsiSeq::AbsoluteMove { horizontal: 0 };
13
14impl Body {
15    /// overwrite:
16    /// write `new_text` to `write`
17    /// and erase the previous text you overwrote with AnsiSeq codes,
18    /// making educated guesses about the height to clear
19    /// from `available_width` variable and `self.buffer` previous text line lengths.
20    /// The goal of `Body` and overwrite is to provide easy text writing to terminal without flicker.
21    pub fn overwrite<T, Writer>(
22        &mut self,
23        new_text: &T,
24        mut write: Writer,
25        available_width: usize,
26    ) -> std::io::Result<()>
27    where
28        Writer: Write,
29        T: ToString,
30    {
31        let new_text = new_text.to_string();
32        let mut next_buffer = if self.buffer.is_empty() {
33            vec![]
34        } else {
35            Vec::with_capacity(self.buffer.capacity())
36        };
37        let mut symbols = vec![];
38
39        if new_text.is_empty() {
40            write!(symbols, "{CLEAR_TIL_EOL}")?;
41            next_buffer.push(0);
42        } else {
43            for (line_no, line) in new_text.lines().enumerate() {
44                next_buffer.push(line.len());
45                if line_no > 0 {
46                    symbols.push(b'\n');
47                }
48                symbols.write_all(line.as_bytes())?;
49                write!(symbols, "{CLEAR_TIL_EOL}")?;
50            }
51        }
52
53        match self.guess_previous_body_height(available_width) {
54            1 => {
55                write!(write, "{JUMP_AT_BEGINNING}")?;
56            }
57            lines if lines > 1 => {
58                let movement = AnsiSeq::MoveLines {
59                    down: 0,
60                    up: (lines - 1) as u16,
61                };
62                write!(write, "{JUMP_AT_BEGINNING}{movement}")?;
63            }
64            _ => {}
65        }
66
67        write!(symbols, "{CLEAR_TIL_EOF}")?;
68        write.write_all(&symbols)?;
69        self.buffer = next_buffer;
70        Ok(())
71    }
72
73    /// create a new Body
74    pub fn new() -> Self {
75        Default::default()
76    }
77
78    fn guess_previous_body_height(&self, available_width: usize) -> usize {
79        let mut lines = 0;
80        for line in &self.buffer {
81            lines += line / available_width + 1;
82        }
83        lines
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90    use std::error::Error;
91    use std::fmt::Write;
92
93    #[test]
94    fn initial() -> Result<(), Box<dyn Error>> {
95        let mut body = Body::default();
96        let mut buf = Vec::new();
97        body.overwrite(&"content", &mut buf, 80)?;
98
99        let string = String::from_utf8(buf.clone())?;
100        assert_eq!("content\\e[0K\\e[0J", string);
101
102        body.overwrite(&"content + content", &mut buf, 80)?;
103        let string = String::from_utf8(buf.clone())?;
104        assert_eq!(
105            "content\\e[0K\\e[0J\\e[0Gcontent + content\\e[0K\\e[0J",
106            string
107        );
108
109        body.overwrite(&"none", &mut buf, 80)?;
110        let string = String::from_utf8(buf.clone())?;
111
112        let mut expected = String::new();
113        expected.write_str("content")?; // first content
114        expected.write_str("\\e[0K")?; // clear til EOL
115        expected.write_str("\\e[0J")?; // clear til EOF
116        expected.write_str("\\e[0G")?; // go far left
117        expected.write_str("content + content")?; // second content
118        expected.write_str("\\e[0K")?; // clear til EOL
119        expected.write_str("\\e[0J")?; // clear til EOF
120        expected.write_str("\\e[0G")?; // go far left
121        expected.write_str("none")?; // third content
122        expected.write_str("\\e[0K")?; // clear til EOL
123        expected.write_str("\\e[0J")?; // clear til EOF
124
125        assert_eq!(expected, string);
126
127        body.overwrite(&"", &mut buf, 80)?;
128
129        expected.write_str("\\e[0G")?; // go far left
130        expected.write_str("\\e[0K")?; // clear til EOL
131        expected.write_str("\\e[0J")?; // clear til EOF
132
133        let string = String::from_utf8(buf.clone())?;
134
135        assert_eq!(expected, string,);
136
137        Ok(())
138    }
139}