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 _ = write!($stream, $literal $($arg)*);
114 _ = $stream.set_color(&ColorSpec::new());
115 }
116 };
117}
118
119#[macro_export]
120macro_rules! cprintln {
121 () => {
122 {
123 let mut stdout = $crate::term::STDOUT.lock().unwrap();
124 $crate::cwriteln!(&mut *stdout);
125 }
126 };
127 ($(fg=$fg:expr,)? $(bg=$bg:expr,)? $(bold=$bold:expr,)? $(dimmed=$dimmed:expr,)? $literal:literal $($arg:tt)*) => {
128 {
129 let mut stdout = $crate::term::STDOUT.lock().unwrap();
130 $crate::cwriteln!(&mut stdout, $(fg=$fg,)? $(bg=$bg,)? $(bold=$bold,)? $(dimmed=$dimmed,)? $literal $($arg)*);
131 }
132 };
133}
134
135#[macro_export]
136macro_rules! cprint {
137 ($(fg=$fg:expr,)? $(bg=$bg:expr,)? $(bold=$bold:expr,)? $(dimmed=$dimmed:expr,)? $literal:literal $($arg:tt)*) => {
138 {
139 let mut stdout = $crate::term::STDOUT.lock().unwrap();
140 $crate::cwrite!(&mut stdout, $(fg=$fg,)? $(bg=$bg,)? $(bold=$bold,)? $(dimmed=$dimmed,)? $literal $($arg)*);
141 }
142 };
143}
144
145#[macro_export]
151macro_rules! cwriteln_rule {
152 ($stream:expr) => {
153 let is_utf8 = *$crate::term::IS_UTF8;
154
155 if is_utf8 {
156 $crate::cwriteln!(
157 $stream,
158 dimmed = true,
159 "{:─>count$}",
160 "",
161 count = $crate::term::term_width() - 1
162 );
163 } else {
164 $crate::cwriteln!(
165 $stream,
166 dimmed = true,
167 "{:->count$}",
168 "",
169 count = $crate::term::term_width() - 1
170 );
171 }
172 };
173 ($stream:expr, $(fg=$fg:expr,)? $(bg=$bg:expr,)? $(bold=$bold:expr,)? $(dimmed=$dimmed:expr,)? $literal:literal $($arg:tt)*) => {
174 use ::unicode_width::UnicodeWidthStr;
175
176 let message = format!($literal $($arg)*);
177 const UNTOUCHABLE: usize = 1 + 8; if $crate::term::term_width() > UNTOUCHABLE {
181 let max_width = $crate::term::term_width() - UNTOUCHABLE;
182 let message = $crate::term::compute_rule_string(&message, max_width);
183 let message_width = message.width();
184
185 let is_utf8 = *$crate::term::IS_UTF8;
186
187 if is_utf8 {
188 $crate::cwrite!($stream, dimmed = true, "{:─>count$}", "", count = max_width - message_width);
189 } else {
190 $crate::cwrite!($stream, dimmed = true, "{:->count$}", "", count = max_width - message_width);
191 }
192
193 if is_utf8 {
194 $crate::cwrite!($stream, dimmed = true, "┨ ");
195 } else {
196 $crate::cwrite!($stream, dimmed = true, "[ ");
197 }
198 $crate::cwrite!($stream, $(fg = $fg,)? $(bg = $bg,)? $(bold = $bold,)? $(dimmed = $dimmed,)? "{message}");
199 if is_utf8 {
200 $crate::cwrite!($stream, dimmed = true, " ┣");
201 } else {
202 $crate::cwrite!($stream, dimmed = true, " ]");
203 }
204
205 if is_utf8 {
206 $crate::cwrite!($stream, dimmed = true, "━━");
207 } else {
208 $crate::cwrite!($stream, dimmed = true, "--");
209 }
210 $crate::cwriteln!($stream);
211 } else {
212 $crate::cwriteln_rule!($stream);
213 }
214 }
215}
216
217#[macro_export]
218macro_rules! cprintln_rule {
219 () => {
220 {
221 let mut stdout = $crate::term::STDOUT.lock().unwrap();
222 $crate::cwriteln_rule!(&mut stdout);
223 }
224 };
225 ($(fg=$fg:expr,)? $(bg=$bg:expr,)? $(bold=$bold:expr,)? $(dimmed=$dimmed:expr,)? $literal:literal $($arg:tt)*) => {
226 {
227 let mut stdout = $crate::term::STDOUT.lock().unwrap();
228 $crate::cwriteln_rule!(&mut *stdout, $(fg=$fg,)? $(bg=$bg,)? $(bold=$bold,)? $(dimmed=$dimmed,)? $literal $($arg)*);
229 }
230 };
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236
237 #[test]
238 fn test_compute_rule_string() {
239 assert_eq!(compute_rule_string("Hello, world!", 10), "Hello…rld!");
240 assert_eq!(compute_rule_string("Hello, world!", 11), "Hello…orld!");
241 assert_eq!(compute_rule_string("Hello, world!", 12), "Hello,…orld!");
242 assert_eq!(compute_rule_string("Hello, world!", 13), "Hello, world!");
243 assert_eq!(compute_rule_string("Hello, world!", 14), "Hello, world!");
244 }
245}