oma_console/
writer.rs

1use std::io::{self, Write};
2
3use console::Term;
4
5/// Gen oma style message prefix
6pub fn gen_prefix(prefix: &str, prefix_len: u16) -> String {
7    if console::measure_text_width(prefix) > (prefix_len - 1).into() {
8        panic!("Line prefix \"{prefix}\" too long!");
9    }
10
11    // Make sure the real_prefix has desired PREFIX_LEN in console
12    let left_padding_size = (prefix_len as usize) - 1 - console::measure_text_width(prefix);
13    let mut real_prefix: String = " ".repeat(left_padding_size);
14    real_prefix.push_str(prefix);
15    real_prefix.push(' ');
16    real_prefix
17}
18
19pub trait Writeln {
20    fn writeln(&self, prefix: &str, msg: &str) -> io::Result<()>;
21}
22
23impl Writeln for Writer {
24    fn writeln(&self, prefix: &str, msg: &str) -> io::Result<()> {
25        let max_len = self.get_max_len();
26
27        let mut res = Ok(());
28
29        writeln_inner(msg, prefix, max_len as usize, self.prefix_len, |t, s| {
30            match t {
31                MessageType::Msg => res = self.term.write_str(s),
32                MessageType::Prefix => res = self.write_prefix(s),
33            };
34        });
35
36        res
37    }
38}
39
40impl Default for Writer {
41    fn default() -> Self {
42        Writer {
43            term: Term::stderr(),
44            prefix_len: 10,
45            limit_max_len: Some(80),
46        }
47    }
48}
49
50pub struct Writer {
51    term: Term,
52    pub prefix_len: u16,
53    limit_max_len: Option<u16>,
54}
55
56impl Writer {
57    pub fn new(prefix_len: u16) -> Self {
58        Self {
59            prefix_len,
60            ..Default::default()
61        }
62    }
63
64    pub fn new_no_limit_length(prefix_len: u16) -> Self {
65        Self {
66            prefix_len,
67            limit_max_len: None,
68            ..Default::default()
69        }
70    }
71
72    pub fn new_stdout() -> Self {
73        Self {
74            term: Term::stdout(),
75            ..Default::default()
76        }
77    }
78
79    /// See environment is terminal
80    pub fn is_terminal(&self) -> bool {
81        self.term.is_term()
82    }
83
84    /// Show terminal cursor
85    pub fn show_cursor(&self) -> io::Result<()> {
86        self.term.show_cursor()?;
87        Ok(())
88    }
89
90    /// Get terminal max len to writer message to terminal
91    pub fn get_max_len(&self) -> u16 {
92        let len = self.get_length();
93
94        if let Some(limit) = self.limit_max_len {
95            if len < limit { len } else { limit }
96        } else {
97            len
98        }
99    }
100
101    /// Get terminal height
102    pub fn get_height(&self) -> u16 {
103        self.term.size_checked().unwrap_or((25, 80)).0
104    }
105
106    /// Get terminal width
107    pub fn get_length(&self) -> u16 {
108        self.term.size_checked().unwrap_or((25, 80)).1
109    }
110
111    /// Get writer to write something to terminal
112    pub fn get_writer(&self) -> Box<dyn Write> {
113        Box::new(self.term.clone())
114    }
115
116    /// Write oma-style message prefix to terminal
117    pub fn write_prefix(&self, prefix: &str) -> io::Result<()> {
118        self.term.write_str(&gen_prefix(prefix, self.prefix_len))?;
119
120        Ok(())
121    }
122
123    pub fn get_prefix_len(&self) -> u16 {
124        self.prefix_len
125    }
126
127    pub fn write_chunks<S: AsRef<str>>(
128        &self,
129        prefix: &str,
130        chunks: &[S],
131        prefix_len: u16,
132    ) -> io::Result<()> {
133        if chunks.is_empty() {
134            return Ok(());
135        }
136
137        let max_len: usize = (self.get_max_len() - prefix_len).into();
138        // Write prefix first
139        self.write_prefix(prefix)?;
140        let mut cur_line_len: usize = prefix_len.into();
141        for chunk in chunks {
142            let chunk = chunk.as_ref();
143            let chunk_len = console::measure_text_width(chunk);
144            // If going to overflow the line, create new line
145            // The `1` is the preceding space
146            if cur_line_len + chunk_len + 1 > max_len {
147                self.term.write_str("\n")?;
148                self.write_prefix("")?;
149                cur_line_len = 0;
150            }
151            self.term.write_str(chunk)?;
152            self.term.write_str(" ")?;
153            cur_line_len += chunk_len + 1;
154        }
155        // Write a new line
156        self.term.write_str("\n")?;
157
158        Ok(())
159    }
160}
161
162pub enum MessageType {
163    Msg,
164    Prefix,
165}
166
167pub fn writeln_inner(
168    msg: &str,
169    prefix: &str,
170    max_len: usize,
171    prefix_len: u16,
172    mut callback: impl FnMut(MessageType, &str),
173) {
174    let len = max_len - prefix_len as usize;
175    let mut first_run = true;
176
177    for i in textwrap::wrap(msg, len) {
178        if first_run {
179            callback(MessageType::Prefix, prefix);
180            first_run = false;
181        } else {
182            callback(MessageType::Prefix, "");
183        }
184
185        callback(MessageType::Msg, &format!("{i}\n"));
186    }
187}