1use std::sync::Mutex;
2pub use termcolor::Color;
3use termcolor::{ColorChoice, StandardStream};
4use unicode_segmentation::UnicodeSegmentation;
5use unicode_width::UnicodeWidthStr;
6
7pub static STDOUT: std::sync::LazyLock<Mutex<StandardStream>> =
8 std::sync::LazyLock::new(|| Mutex::new(StandardStream::stdout(ColorChoice::Auto)));
9
10pub static IS_UTF8: std::sync::LazyLock<bool> = std::sync::LazyLock::new(|| {
11 utf8_supported::utf8_supported() == utf8_supported::Utf8Support::UTF8
12});
13
14pub fn term_width() -> usize {
17 termsize::get().map(|s| (s.cols - 1) as usize).unwrap_or(79)
18}
19
20pub fn compute_rule_string(message: &str, max_width: usize) -> String {
21 if message.width() <= max_width {
22 message.to_string()
23 } else {
24 let mut chars = message.graphemes(true);
25
26 let mut start = String::with_capacity(max_width / 2);
27 let mut end = String::with_capacity(max_width / 2);
28 let ellipsis = "…";
29
30 loop {
31 if let Some(grapheme) = chars.next() {
32 let prev_len = start.len();
33 start.push_str(grapheme);
34 if start.width() + end.width() + ellipsis.width() > max_width {
35 start.truncate(prev_len);
36 break;
37 }
38 } else {
39 break;
40 }
41 if let Some(grapheme) = chars.next_back() {
42 let prev_len = end.len();
43 end.insert_str(0, grapheme);
44 if start.width() + end.width() + ellipsis.width() > max_width {
45 end.drain(0..(end.len() - prev_len));
46 break;
47 }
48 } else {
49 break;
50 }
51 }
52
53 let s = format!("{}{}{}", start, ellipsis, end);
54 debug_assert!(s.width() <= max_width);
55 s
56 }
57}
58
59#[macro_export]
60macro_rules! println {
61 ($($arg:tt)*) => {
62 {
63 use std::io::Write;
64 _ = writeln!($crate::term::STDOUT.lock().unwrap(), $($arg)*);
65 }
66 };
67}
68
69#[macro_export]
70macro_rules! cwriteln {
71 ($stream:expr) => {
72 {
73 use termcolor::{WriteColor, ColorSpec};
74 let stream: &mut dyn WriteColor = &mut $stream;
75 _ = stream.set_color(&ColorSpec::new());
76 _ = writeln!(stream);
77 }
78 };
79 ($stream:expr, $(fg=$fg:expr,)? $(bg=$bg:expr,)? $(bold=$bold:expr,)? $(dimmed=$dimmed:expr,)? $literal:literal $($arg:tt)*) => {
80 {
81 #[allow(unused_imports)]
82 use std::io::Write;
83 $crate::cwrite!($stream, $(fg=$fg,)? $(bg=$bg,)? $(bold=$bold,)? $(dimmed=$dimmed,)? $literal $($arg)*);
84 _ = writeln!($stream);
85 }
86 };
87}
88
89#[macro_export]
90macro_rules! cwrite {
91 ($stream:expr, $(fg=$fg:expr,)? $(bg=$bg:expr,)? $(bold=$bold:expr,)? $(dimmed=$dimmed:expr,)? $literal:literal $($arg:tt)*) => {
92 {
93 #[allow(unused_imports)]
94 use termcolor::{WriteColor, ColorSpec};
95 #[allow(unused_imports)]
96 use std::io::Write;
97
98 #[allow(unused_mut)]
99 let mut color = ColorSpec::new();
100 $(
101 color.set_bg(Some($bg));
102 )?
103 $(
104 color.set_fg(Some($fg));
105 )?
106 $(
107 color.set_bold($bold);
108 )?
109 $(
110 color.set_dimmed($dimmed);
111 )?
112 _ = $stream.set_color(&color);
113 let mut s = format!($literal $($arg)*);
114 if s.contains('\x1b') {
115 s = s.replace('\x1b', "\u{241B}"); }
117 _ = write!($stream, "{s}");
118 _ = $stream.set_color(&ColorSpec::new());
119 }
120 };
121}
122
123#[macro_export]
124macro_rules! cprintln {
125 () => {
126 {
127 let mut stdout = $crate::term::STDOUT.lock().unwrap();
128 $crate::cwriteln!(&mut *stdout);
129 }
130 };
131 ($(fg=$fg:expr,)? $(bg=$bg:expr,)? $(bold=$bold:expr,)? $(dimmed=$dimmed:expr,)? $literal:literal $($arg:tt)*) => {
132 {
133 let mut stdout = $crate::term::STDOUT.lock().unwrap();
134 $crate::cwriteln!(&mut stdout, $(fg=$fg,)? $(bg=$bg,)? $(bold=$bold,)? $(dimmed=$dimmed,)? $literal $($arg)*);
135 }
136 };
137}
138
139#[macro_export]
140macro_rules! cprint {
141 ($(fg=$fg:expr,)? $(bg=$bg:expr,)? $(bold=$bold:expr,)? $(dimmed=$dimmed:expr,)? $literal:literal $($arg:tt)*) => {
142 {
143 let mut stdout = $crate::term::STDOUT.lock().unwrap();
144 $crate::cwrite!(&mut stdout, $(fg=$fg,)? $(bg=$bg,)? $(bold=$bold,)? $(dimmed=$dimmed,)? $literal $($arg)*);
145 }
146 };
147}
148
149#[macro_export]
155macro_rules! cwriteln_rule {
156 ($stream:expr) => {
157 let is_utf8 = *$crate::term::IS_UTF8;
158
159 if is_utf8 {
160 $crate::cwriteln!(
161 $stream,
162 dimmed = true,
163 "{:─>count$}",
164 "",
165 count = $crate::term::term_width() - 1
166 );
167 } else {
168 $crate::cwriteln!(
169 $stream,
170 dimmed = true,
171 "{:->count$}",
172 "",
173 count = $crate::term::term_width() - 1
174 );
175 }
176 };
177 ($stream:expr, $(fg=$fg:expr,)? $(bg=$bg:expr,)? $(bold=$bold:expr,)? $(dimmed=$dimmed:expr,)? $literal:literal $($arg:tt)*) => {
178 use ::unicode_width::UnicodeWidthStr;
179
180 let message = format!($literal $($arg)*);
181 const UNTOUCHABLE: usize = 1 + 8; if $crate::term::term_width() > UNTOUCHABLE {
185 let max_width = $crate::term::term_width() - UNTOUCHABLE;
186 let message = $crate::term::compute_rule_string(&message, max_width);
187 let message_width = message.width();
188
189 let is_utf8 = *$crate::term::IS_UTF8;
190
191 if is_utf8 {
192 $crate::cwrite!($stream, dimmed = true, "{:─>count$}", "", count = max_width - message_width);
193 } else {
194 $crate::cwrite!($stream, dimmed = true, "{:->count$}", "", count = max_width - message_width);
195 }
196
197 if is_utf8 {
198 $crate::cwrite!($stream, dimmed = true, "┨ ");
199 } else {
200 $crate::cwrite!($stream, dimmed = true, "[ ");
201 }
202 $crate::cwrite!($stream, $(fg = $fg,)? $(bg = $bg,)? $(bold = $bold,)? $(dimmed = $dimmed,)? "{message}");
203 if is_utf8 {
204 $crate::cwrite!($stream, dimmed = true, " ┣");
205 } else {
206 $crate::cwrite!($stream, dimmed = true, " ]");
207 }
208
209 if is_utf8 {
210 $crate::cwrite!($stream, dimmed = true, "━━");
211 } else {
212 $crate::cwrite!($stream, dimmed = true, "--");
213 }
214 $crate::cwriteln!($stream);
215 } else {
216 $crate::cwriteln_rule!($stream);
217 }
218 }
219}
220
221#[macro_export]
222macro_rules! cprintln_rule {
223 () => {
224 {
225 let mut stdout = $crate::term::STDOUT.lock().unwrap();
226 $crate::cwriteln_rule!(&mut stdout);
227 }
228 };
229 ($(fg=$fg:expr,)? $(bg=$bg:expr,)? $(bold=$bold:expr,)? $(dimmed=$dimmed:expr,)? $literal:literal $($arg:tt)*) => {
230 {
231 let mut stdout = $crate::term::STDOUT.lock().unwrap();
232 $crate::cwriteln_rule!(&mut *stdout, $(fg=$fg,)? $(bg=$bg,)? $(bold=$bold,)? $(dimmed=$dimmed,)? $literal $($arg)*);
233 }
234 };
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240
241 #[test]
242 fn test_compute_rule_string() {
243 assert_eq!(compute_rule_string("Hello, world!", 10), "Hello…rld!");
244 assert_eq!(compute_rule_string("Hello, world!", 11), "Hello…orld!");
245 assert_eq!(compute_rule_string("Hello, world!", 12), "Hello,…orld!");
246 assert_eq!(compute_rule_string("Hello, world!", 13), "Hello, world!");
247 assert_eq!(compute_rule_string("Hello, world!", 14), "Hello, world!");
248 }
249}