1extern crate ansi_escapes;
2
3use std::io::Error;
4use std::io::Write;
5
6pub struct LogUpdate<W: Write> {
8 stream: W,
9 previous_line_count: u16,
10 cursor_is_hidden: bool,
11}
12
13impl<W: Write> LogUpdate<W> {
14 pub fn new(mut stream: W) -> Result<Self, Error> {
16 try!(write!(stream, "{}", ansi_escapes::CursorHide));
17 try!(stream.flush());
18
19 Ok(LogUpdate { stream: stream, previous_line_count: 0, cursor_is_hidden: true })
20 }
21
22 pub fn render(&mut self, text: &str) -> Result<(), Error> {
24 try!(write!(self.stream, "{}{}\n", ansi_escapes::EraseLines(self.previous_line_count), text));
25 try!(self.stream.flush());
26
27 self.previous_line_count = text.chars().filter(|x| *x == '\n').count() as u16 + 2;
28
29 Ok(())
30 }
31
32 pub fn clear(&mut self) -> Result<(), Error> {
34 try!(write!(self.stream, "{}", ansi_escapes::EraseLines(self.previous_line_count)));
35 try!(self.stream.flush());
36
37 self.previous_line_count = 0;
38
39 Ok(())
40 }
41
42 pub fn done(&mut self) -> Result<(), Error> {
45 if self.cursor_is_hidden {
46 try!(write!(self.stream, "{}", ansi_escapes::CursorShow));
47 try!(self.stream.flush());
48 }
49
50 self.previous_line_count = 0;
51 self.cursor_is_hidden = false;
52
53 Ok(())
54 }
55}
56
57impl<W: Write> Drop for LogUpdate<W> {
58 fn drop(&mut self) {
59 if self.cursor_is_hidden {
60 write!(self.stream, "{}", ansi_escapes::CursorShow).unwrap();
61 self.stream.flush().unwrap();
62 }
63 }
64}
65
66#[cfg(test)]
67extern crate tempfile;
68
69#[cfg(test)]
70mod tests {
71 use tempfile::tempfile;
72 use std::fs::File;
73 use std::io::{Read, Seek, SeekFrom};
74
75 use super::LogUpdate;
76
77 #[test]
78 fn it_handles_most_common_use_case() {
79 let mut file: File = tempfile().unwrap();
80 let mut log_update = LogUpdate::new(file.try_clone().unwrap()).unwrap();
81
82 log_update.render("test1").unwrap();
83 log_update.render("test2").unwrap();
84
85 log_update.done().unwrap();
86
87 file.seek(SeekFrom::Start(0)).unwrap();
88
89 let mut buf = String::new();
90 file.read_to_string(&mut buf).unwrap();
91
92 assert_eq!(concat!("\x1B[?25l", "test1\n", "\x1B[1000D\x1B[K\x1B[1A\x1B[1000D\x1B[K", "test2\n", "\x1B[?25h"), buf);
93 }
94
95 #[test]
96 fn it_restores_cursor_when_dropped() {
97 let mut file: File = tempfile().unwrap();
98 let mut log_update = LogUpdate::new(file.try_clone().unwrap()).unwrap();
99
100 log_update.render("test").unwrap();
101
102 drop(log_update);
103
104 file.seek(SeekFrom::Start(0)).unwrap();
105
106 let mut buf = String::new();
107 file.read_to_string(&mut buf).unwrap();
108
109 assert_eq!(concat!("\x1B[?25l", "test\n", "\x1B[?25h"), buf);
110 }
111}