Skip to main content

nitro_shared/util/
print.rs

1use std::fmt::Debug;
2use std::io::Write;
3use std::sync::Arc;
4use std::sync::atomic::{AtomicUsize, Ordering};
5
6/// String used program-wide for most indentation
7pub const INDENT_STR: &str = "    ";
8
9/// Used to print text that is replaced
10#[derive(Debug, Clone)]
11pub struct ReplPrinter {
12	chars_written: Arc<AtomicUsize>,
13	finished: bool,
14	options: PrintOptions,
15}
16
17impl ReplPrinter {
18	/// Make a new ReplPrinter with a verbosity option.
19	/// If that option is false, then nothing will be printed
20	pub fn new(verbose: bool) -> Self {
21		Self::from_options(PrintOptions::new(verbose, 0))
22	}
23
24	/// Make a new ReplPrinter using a set of print options
25	pub fn from_options(options: PrintOptions) -> Self {
26		Self {
27			chars_written: Arc::new(AtomicUsize::new(0)),
28			finished: false,
29			options,
30		}
31	}
32
33	/// Set the indent level of the printer
34	pub fn indent(&mut self, indent: usize) {
35		self.options.indent = indent;
36		self.options.indent_str = make_indent(self.options.indent);
37	}
38
39	/// Replace the current line with spaces
40	pub fn clearline(&self) {
41		if !self.options.verbose {
42			return;
43		}
44
45		let mut lock = std::io::stdout().lock();
46
47		let chars_written = self.chars_written.load(Ordering::Relaxed);
48		if chars_written == 0 {
49			return;
50		}
51
52		let _ = write!(&mut lock, "\r");
53		for _ in 0..chars_written {
54			let _ = write!(&mut lock, " ");
55		}
56		self.chars_written.store(0, Ordering::Relaxed);
57		let _ = lock.flush();
58	}
59
60	/// Print text to the output, replacing the current line
61	pub fn print(&self, text: &str) {
62		if !self.options.verbose {
63			return;
64		}
65		let mut lock = std::io::stdout().lock();
66
67		// Write the text
68		let _ = write!(&mut lock, "\r{}{text}", self.options.indent_str);
69
70		// Calculate the amount written
71		let written = get_terminal_width(text) + self.options.indent_str.chars().count();
72
73		// Clear leftover characters from the last print
74		let chars_written = self.chars_written.load(Ordering::Relaxed);
75		if written < chars_written {
76			let _ = write!(&mut lock, "{}", " ".repeat(chars_written - written));
77		}
78
79		self.chars_written.store(written, Ordering::Relaxed);
80		let _ = lock.flush();
81	}
82
83	/// Print text on a new line
84	pub fn println(&self, text: &str) {
85		self.newline();
86		self.print(text);
87	}
88
89	/// Finish printing and make a newline
90	pub fn finish(&mut self) {
91		if self.finished {
92			return;
93		}
94		if self.chars_written.load(Ordering::Relaxed) != 0 {
95			self.newline();
96		}
97		self.finished = true;
98	}
99
100	/// Force this printer to not make a newline when it finishes
101	pub fn force_finished(&mut self) {
102		self.finished = true;
103	}
104
105	/// Make a line break
106	pub fn newline(&self) {
107		let mut lock = std::io::stdout().lock();
108
109		self.chars_written.store(0, Ordering::Relaxed);
110
111		let _ = writeln!(&mut lock);
112		std::mem::drop(lock);
113	}
114}
115
116impl Drop for ReplPrinter {
117	fn drop(&mut self) {
118		self.finish();
119	}
120}
121
122/// Create the characters for an indent count
123pub fn make_indent(indent: usize) -> String {
124	INDENT_STR.repeat(indent)
125}
126
127/// Set of options for printing output
128#[derive(Debug, Clone)]
129pub struct PrintOptions {
130	/// Whether to print at all
131	pub verbose: bool,
132	/// Indent level
133	pub indent: usize,
134	/// Indent as a string
135	pub indent_str: String,
136}
137
138impl PrintOptions {
139	/// Create a new PrintOptions with verbosity and indent level settings
140	pub fn new(verbose: bool, indent: usize) -> Self {
141		Self {
142			verbose,
143			indent,
144			indent_str: make_indent(indent),
145		}
146	}
147
148	/// Increase the indent of the PrintOptions
149	pub fn increase_indent(opt: &Self) -> Self {
150		let mut out = opt.clone();
151		out.indent += 1;
152		out.indent_str = make_indent(out.indent);
153		out
154	}
155}
156
157/// Calculate how many characters long something will appear to be in the terminal,
158/// skipping over escape sequences and the such
159pub fn get_terminal_width(text: &str) -> usize {
160	let esc = 0o33 as char;
161	let mut out = 0;
162	let mut in_escape = false;
163	for c in text.chars() {
164		if c == esc {
165			in_escape = true;
166		}
167
168		if !in_escape {
169			out += 1;
170		}
171
172		if c == 'm' {
173			in_escape = false;
174		}
175	}
176	out
177}
178
179#[cfg(test)]
180mod tests {
181	use super::*;
182
183	#[test]
184	fn test_terminal_width() {
185		assert_eq!(get_terminal_width("\u{001b}[16mHello"), 5);
186	}
187}