1 | use std::borrow::Cow; |
---|---|
2 | use std::collections::BTreeSet; |
3 | use std::env; |
4 | use std::fmt; |
5 | use std::sync::atomic::{AtomicBool, Ordering}; |
6 | |
7 | use once_cell::sync::Lazy; |
8 | |
9 | use crate::term::{wants_emoji, Term}; |
10 | |
11 | #[cfg(feature = "ansi-parsing")] |
12 | use crate::ansi::{strip_ansi_codes, AnsiCodeIterator}; |
13 | |
14 | #[cfg(not(feature = "ansi-parsing"))] |
15 | fn strip_ansi_codes(s: &str) -> &str { |
16 | s |
17 | } |
18 | |
19 | fn default_colors_enabled(out: &Term) -> bool { |
20 | (out.features().colors_supported() |
21 | && &env::var("CLICOLOR").unwrap_or_else(|_| "1".into()) != "0") |
22 | || &env::var("CLICOLOR_FORCE").unwrap_or_else(|_| "0".into()) != "0" |
23 | } |
24 | |
25 | static STDOUT_COLORS: Lazy<AtomicBool> = |
26 | Lazy::new(|| AtomicBool::new(default_colors_enabled(&Term::stdout()))); |
27 | static STDERR_COLORS: Lazy<AtomicBool> = |
28 | Lazy::new(|| AtomicBool::new(default_colors_enabled(&Term::stderr()))); |
29 | |
30 | /// Returns `true` if colors should be enabled for stdout. |
31 | /// |
32 | /// This honors the [clicolors spec](http://bixense.com/clicolors/). |
33 | /// |
34 | /// * `CLICOLOR != 0`: ANSI colors are supported and should be used when the program isn't piped. |
35 | /// * `CLICOLOR == 0`: Don't output ANSI color escape codes. |
36 | /// * `CLICOLOR_FORCE != 0`: ANSI colors should be enabled no matter what. |
37 | #[inline] |
38 | pub fn colors_enabled() -> bool { |
39 | STDOUT_COLORS.load(order:Ordering::Relaxed) |
40 | } |
41 | |
42 | /// Forces colorization on or off for stdout. |
43 | /// |
44 | /// This overrides the default for the current process and changes the return value of the |
45 | /// `colors_enabled` function. |
46 | #[inline] |
47 | pub fn set_colors_enabled(val: bool) { |
48 | STDOUT_COLORS.store(val, order:Ordering::Relaxed) |
49 | } |
50 | |
51 | /// Returns `true` if colors should be enabled for stderr. |
52 | /// |
53 | /// This honors the [clicolors spec](http://bixense.com/clicolors/). |
54 | /// |
55 | /// * `CLICOLOR != 0`: ANSI colors are supported and should be used when the program isn't piped. |
56 | /// * `CLICOLOR == 0`: Don't output ANSI color escape codes. |
57 | /// * `CLICOLOR_FORCE != 0`: ANSI colors should be enabled no matter what. |
58 | #[inline] |
59 | pub fn colors_enabled_stderr() -> bool { |
60 | STDERR_COLORS.load(order:Ordering::Relaxed) |
61 | } |
62 | |
63 | /// Forces colorization on or off for stderr. |
64 | /// |
65 | /// This overrides the default for the current process and changes the return value of the |
66 | /// `colors_enabled` function. |
67 | #[inline] |
68 | pub fn set_colors_enabled_stderr(val: bool) { |
69 | STDERR_COLORS.store(val, order:Ordering::Relaxed) |
70 | } |
71 | |
72 | /// Measure the width of a string in terminal characters. |
73 | pub fn measure_text_width(s: &str) -> usize { |
74 | str_width(&strip_ansi_codes(s)) |
75 | } |
76 | |
77 | /// A terminal color. |
78 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] |
79 | pub enum Color { |
80 | Black, |
81 | Red, |
82 | Green, |
83 | Yellow, |
84 | Blue, |
85 | Magenta, |
86 | Cyan, |
87 | White, |
88 | Color256(u8), |
89 | } |
90 | |
91 | impl Color { |
92 | #[inline] |
93 | fn ansi_num(self) -> usize { |
94 | match self { |
95 | Color::Black => 0, |
96 | Color::Red => 1, |
97 | Color::Green => 2, |
98 | Color::Yellow => 3, |
99 | Color::Blue => 4, |
100 | Color::Magenta => 5, |
101 | Color::Cyan => 6, |
102 | Color::White => 7, |
103 | Color::Color256(x) => x as usize, |
104 | } |
105 | } |
106 | |
107 | #[inline] |
108 | fn is_color256(self) -> bool { |
109 | #[allow(clippy::match_like_matches_macro)] |
110 | match self { |
111 | Color::Color256(_) => true, |
112 | _ => false, |
113 | } |
114 | } |
115 | } |
116 | |
117 | /// A terminal style attribute. |
118 | #[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd)] |
119 | pub enum Attribute { |
120 | Bold, |
121 | Dim, |
122 | Italic, |
123 | Underlined, |
124 | Blink, |
125 | BlinkFast, |
126 | Reverse, |
127 | Hidden, |
128 | StrikeThrough, |
129 | } |
130 | |
131 | impl Attribute { |
132 | #[inline] |
133 | fn ansi_num(self) -> usize { |
134 | match self { |
135 | Attribute::Bold => 1, |
136 | Attribute::Dim => 2, |
137 | Attribute::Italic => 3, |
138 | Attribute::Underlined => 4, |
139 | Attribute::Blink => 5, |
140 | Attribute::BlinkFast => 6, |
141 | Attribute::Reverse => 7, |
142 | Attribute::Hidden => 8, |
143 | Attribute::StrikeThrough => 9, |
144 | } |
145 | } |
146 | } |
147 | |
148 | /// Defines the alignment for padding operations. |
149 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] |
150 | pub enum Alignment { |
151 | Left, |
152 | Center, |
153 | Right, |
154 | } |
155 | |
156 | /// A stored style that can be applied. |
157 | #[derive(Clone, Debug, PartialEq, Eq)] |
158 | pub struct Style { |
159 | fg: Option<Color>, |
160 | bg: Option<Color>, |
161 | fg_bright: bool, |
162 | bg_bright: bool, |
163 | attrs: BTreeSet<Attribute>, |
164 | force: Option<bool>, |
165 | for_stderr: bool, |
166 | } |
167 | |
168 | impl Default for Style { |
169 | fn default() -> Style { |
170 | Style::new() |
171 | } |
172 | } |
173 | |
174 | impl Style { |
175 | /// Returns an empty default style. |
176 | pub fn new() -> Style { |
177 | Style { |
178 | fg: None, |
179 | bg: None, |
180 | fg_bright: false, |
181 | bg_bright: false, |
182 | attrs: BTreeSet::new(), |
183 | force: None, |
184 | for_stderr: false, |
185 | } |
186 | } |
187 | |
188 | /// Creates a style from a dotted string. |
189 | /// |
190 | /// Effectively the string is split at each dot and then the |
191 | /// terms in between are applied. For instance `red.on_blue` will |
192 | /// create a string that is red on blue background. `9.on_12` is |
193 | /// the same, but using 256 color numbers. Unknown terms are |
194 | /// ignored. |
195 | pub fn from_dotted_str(s: &str) -> Style { |
196 | let mut rv = Style::new(); |
197 | for part in s.split('.') { |
198 | rv = match part { |
199 | "black"=> rv.black(), |
200 | "red"=> rv.red(), |
201 | "green"=> rv.green(), |
202 | "yellow"=> rv.yellow(), |
203 | "blue"=> rv.blue(), |
204 | "magenta"=> rv.magenta(), |
205 | "cyan"=> rv.cyan(), |
206 | "white"=> rv.white(), |
207 | "bright"=> rv.bright(), |
208 | "on_black"=> rv.on_black(), |
209 | "on_red"=> rv.on_red(), |
210 | "on_green"=> rv.on_green(), |
211 | "on_yellow"=> rv.on_yellow(), |
212 | "on_blue"=> rv.on_blue(), |
213 | "on_magenta"=> rv.on_magenta(), |
214 | "on_cyan"=> rv.on_cyan(), |
215 | "on_white"=> rv.on_white(), |
216 | "on_bright"=> rv.on_bright(), |
217 | "bold"=> rv.bold(), |
218 | "dim"=> rv.dim(), |
219 | "underlined"=> rv.underlined(), |
220 | "blink"=> rv.blink(), |
221 | "blink_fast"=> rv.blink_fast(), |
222 | "reverse"=> rv.reverse(), |
223 | "hidden"=> rv.hidden(), |
224 | "strikethrough"=> rv.strikethrough(), |
225 | on_c if on_c.starts_with("on_") => { |
226 | if let Ok(n) = on_c[3..].parse::<u8>() { |
227 | rv.on_color256(n) |
228 | } else { |
229 | continue; |
230 | } |
231 | } |
232 | c => { |
233 | if let Ok(n) = c.parse::<u8>() { |
234 | rv.color256(n) |
235 | } else { |
236 | continue; |
237 | } |
238 | } |
239 | }; |
240 | } |
241 | rv |
242 | } |
243 | |
244 | /// Apply the style to something that can be displayed. |
245 | pub fn apply_to<D>(&self, val: D) -> StyledObject<D> { |
246 | StyledObject { |
247 | style: self.clone(), |
248 | val, |
249 | } |
250 | } |
251 | |
252 | /// Forces styling on or off. |
253 | /// |
254 | /// This overrides the automatic detection. |
255 | #[inline] |
256 | pub fn force_styling(mut self, value: bool) -> Style { |
257 | self.force = Some(value); |
258 | self |
259 | } |
260 | |
261 | /// Specifies that style is applying to something being written on stderr. |
262 | #[inline] |
263 | pub fn for_stderr(mut self) -> Style { |
264 | self.for_stderr = true; |
265 | self |
266 | } |
267 | |
268 | /// Specifies that style is applying to something being written on stdout. |
269 | /// |
270 | /// This is the default behaviour. |
271 | #[inline] |
272 | pub fn for_stdout(mut self) -> Style { |
273 | self.for_stderr = false; |
274 | self |
275 | } |
276 | |
277 | /// Sets a foreground color. |
278 | #[inline] |
279 | pub fn fg(mut self, color: Color) -> Style { |
280 | self.fg = Some(color); |
281 | self |
282 | } |
283 | |
284 | /// Sets a background color. |
285 | #[inline] |
286 | pub fn bg(mut self, color: Color) -> Style { |
287 | self.bg = Some(color); |
288 | self |
289 | } |
290 | |
291 | /// Adds a attr. |
292 | #[inline] |
293 | pub fn attr(mut self, attr: Attribute) -> Style { |
294 | self.attrs.insert(attr); |
295 | self |
296 | } |
297 | |
298 | #[inline] |
299 | pub fn black(self) -> Style { |
300 | self.fg(Color::Black) |
301 | } |
302 | #[inline] |
303 | pub fn red(self) -> Style { |
304 | self.fg(Color::Red) |
305 | } |
306 | #[inline] |
307 | pub fn green(self) -> Style { |
308 | self.fg(Color::Green) |
309 | } |
310 | #[inline] |
311 | pub fn yellow(self) -> Style { |
312 | self.fg(Color::Yellow) |
313 | } |
314 | #[inline] |
315 | pub fn blue(self) -> Style { |
316 | self.fg(Color::Blue) |
317 | } |
318 | #[inline] |
319 | pub fn magenta(self) -> Style { |
320 | self.fg(Color::Magenta) |
321 | } |
322 | #[inline] |
323 | pub fn cyan(self) -> Style { |
324 | self.fg(Color::Cyan) |
325 | } |
326 | #[inline] |
327 | pub fn white(self) -> Style { |
328 | self.fg(Color::White) |
329 | } |
330 | #[inline] |
331 | pub fn color256(self, color: u8) -> Style { |
332 | self.fg(Color::Color256(color)) |
333 | } |
334 | |
335 | #[inline] |
336 | pub fn bright(mut self) -> Style { |
337 | self.fg_bright = true; |
338 | self |
339 | } |
340 | |
341 | #[inline] |
342 | pub fn on_black(self) -> Style { |
343 | self.bg(Color::Black) |
344 | } |
345 | #[inline] |
346 | pub fn on_red(self) -> Style { |
347 | self.bg(Color::Red) |
348 | } |
349 | #[inline] |
350 | pub fn on_green(self) -> Style { |
351 | self.bg(Color::Green) |
352 | } |
353 | #[inline] |
354 | pub fn on_yellow(self) -> Style { |
355 | self.bg(Color::Yellow) |
356 | } |
357 | #[inline] |
358 | pub fn on_blue(self) -> Style { |
359 | self.bg(Color::Blue) |
360 | } |
361 | #[inline] |
362 | pub fn on_magenta(self) -> Style { |
363 | self.bg(Color::Magenta) |
364 | } |
365 | #[inline] |
366 | pub fn on_cyan(self) -> Style { |
367 | self.bg(Color::Cyan) |
368 | } |
369 | #[inline] |
370 | pub fn on_white(self) -> Style { |
371 | self.bg(Color::White) |
372 | } |
373 | #[inline] |
374 | pub fn on_color256(self, color: u8) -> Style { |
375 | self.bg(Color::Color256(color)) |
376 | } |
377 | |
378 | #[inline] |
379 | pub fn on_bright(mut self) -> Style { |
380 | self.bg_bright = true; |
381 | self |
382 | } |
383 | |
384 | #[inline] |
385 | pub fn bold(self) -> Style { |
386 | self.attr(Attribute::Bold) |
387 | } |
388 | #[inline] |
389 | pub fn dim(self) -> Style { |
390 | self.attr(Attribute::Dim) |
391 | } |
392 | #[inline] |
393 | pub fn italic(self) -> Style { |
394 | self.attr(Attribute::Italic) |
395 | } |
396 | #[inline] |
397 | pub fn underlined(self) -> Style { |
398 | self.attr(Attribute::Underlined) |
399 | } |
400 | #[inline] |
401 | pub fn blink(self) -> Style { |
402 | self.attr(Attribute::Blink) |
403 | } |
404 | #[inline] |
405 | pub fn blink_fast(self) -> Style { |
406 | self.attr(Attribute::BlinkFast) |
407 | } |
408 | #[inline] |
409 | pub fn reverse(self) -> Style { |
410 | self.attr(Attribute::Reverse) |
411 | } |
412 | #[inline] |
413 | pub fn hidden(self) -> Style { |
414 | self.attr(Attribute::Hidden) |
415 | } |
416 | #[inline] |
417 | pub fn strikethrough(self) -> Style { |
418 | self.attr(Attribute::StrikeThrough) |
419 | } |
420 | } |
421 | |
422 | /// Wraps an object for formatting for styling. |
423 | /// |
424 | /// Example: |
425 | /// |
426 | /// ```rust,no_run |
427 | /// # use console::style; |
428 | /// format!("Hello {}", style( "World").cyan()); |
429 | /// ``` |
430 | /// |
431 | /// This is a shortcut for making a new style and applying it |
432 | /// to a value: |
433 | /// |
434 | /// ```rust,no_run |
435 | /// # use console::Style; |
436 | /// format!("Hello {}", Style::new().cyan().apply_to( "World")); |
437 | /// ``` |
438 | pub fn style<D>(val: D) -> StyledObject<D> { |
439 | Style::new().apply_to(val) |
440 | } |
441 | |
442 | /// A formatting wrapper that can be styled for a terminal. |
443 | #[derive(Clone)] |
444 | pub struct StyledObject<D> { |
445 | style: Style, |
446 | val: D, |
447 | } |
448 | |
449 | impl<D> StyledObject<D> { |
450 | /// Forces styling on or off. |
451 | /// |
452 | /// This overrides the automatic detection. |
453 | #[inline] |
454 | pub fn force_styling(mut self, value: bool) -> StyledObject<D> { |
455 | self.style = self.style.force_styling(value); |
456 | self |
457 | } |
458 | |
459 | /// Specifies that style is applying to something being written on stderr |
460 | #[inline] |
461 | pub fn for_stderr(mut self) -> StyledObject<D> { |
462 | self.style = self.style.for_stderr(); |
463 | self |
464 | } |
465 | |
466 | /// Specifies that style is applying to something being written on stdout |
467 | /// |
468 | /// This is the default |
469 | #[inline] |
470 | pub fn for_stdout(mut self) -> StyledObject<D> { |
471 | self.style = self.style.for_stdout(); |
472 | self |
473 | } |
474 | |
475 | /// Sets a foreground color. |
476 | #[inline] |
477 | pub fn fg(mut self, color: Color) -> StyledObject<D> { |
478 | self.style = self.style.fg(color); |
479 | self |
480 | } |
481 | |
482 | /// Sets a background color. |
483 | #[inline] |
484 | pub fn bg(mut self, color: Color) -> StyledObject<D> { |
485 | self.style = self.style.bg(color); |
486 | self |
487 | } |
488 | |
489 | /// Adds a attr. |
490 | #[inline] |
491 | pub fn attr(mut self, attr: Attribute) -> StyledObject<D> { |
492 | self.style = self.style.attr(attr); |
493 | self |
494 | } |
495 | |
496 | #[inline] |
497 | pub fn black(self) -> StyledObject<D> { |
498 | self.fg(Color::Black) |
499 | } |
500 | #[inline] |
501 | pub fn red(self) -> StyledObject<D> { |
502 | self.fg(Color::Red) |
503 | } |
504 | #[inline] |
505 | pub fn green(self) -> StyledObject<D> { |
506 | self.fg(Color::Green) |
507 | } |
508 | #[inline] |
509 | pub fn yellow(self) -> StyledObject<D> { |
510 | self.fg(Color::Yellow) |
511 | } |
512 | #[inline] |
513 | pub fn blue(self) -> StyledObject<D> { |
514 | self.fg(Color::Blue) |
515 | } |
516 | #[inline] |
517 | pub fn magenta(self) -> StyledObject<D> { |
518 | self.fg(Color::Magenta) |
519 | } |
520 | #[inline] |
521 | pub fn cyan(self) -> StyledObject<D> { |
522 | self.fg(Color::Cyan) |
523 | } |
524 | #[inline] |
525 | pub fn white(self) -> StyledObject<D> { |
526 | self.fg(Color::White) |
527 | } |
528 | #[inline] |
529 | pub fn color256(self, color: u8) -> StyledObject<D> { |
530 | self.fg(Color::Color256(color)) |
531 | } |
532 | |
533 | #[inline] |
534 | pub fn bright(mut self) -> StyledObject<D> { |
535 | self.style = self.style.bright(); |
536 | self |
537 | } |
538 | |
539 | #[inline] |
540 | pub fn on_black(self) -> StyledObject<D> { |
541 | self.bg(Color::Black) |
542 | } |
543 | #[inline] |
544 | pub fn on_red(self) -> StyledObject<D> { |
545 | self.bg(Color::Red) |
546 | } |
547 | #[inline] |
548 | pub fn on_green(self) -> StyledObject<D> { |
549 | self.bg(Color::Green) |
550 | } |
551 | #[inline] |
552 | pub fn on_yellow(self) -> StyledObject<D> { |
553 | self.bg(Color::Yellow) |
554 | } |
555 | #[inline] |
556 | pub fn on_blue(self) -> StyledObject<D> { |
557 | self.bg(Color::Blue) |
558 | } |
559 | #[inline] |
560 | pub fn on_magenta(self) -> StyledObject<D> { |
561 | self.bg(Color::Magenta) |
562 | } |
563 | #[inline] |
564 | pub fn on_cyan(self) -> StyledObject<D> { |
565 | self.bg(Color::Cyan) |
566 | } |
567 | #[inline] |
568 | pub fn on_white(self) -> StyledObject<D> { |
569 | self.bg(Color::White) |
570 | } |
571 | #[inline] |
572 | pub fn on_color256(self, color: u8) -> StyledObject<D> { |
573 | self.bg(Color::Color256(color)) |
574 | } |
575 | |
576 | #[inline] |
577 | pub fn on_bright(mut self) -> StyledObject<D> { |
578 | self.style = self.style.on_bright(); |
579 | self |
580 | } |
581 | |
582 | #[inline] |
583 | pub fn bold(self) -> StyledObject<D> { |
584 | self.attr(Attribute::Bold) |
585 | } |
586 | #[inline] |
587 | pub fn dim(self) -> StyledObject<D> { |
588 | self.attr(Attribute::Dim) |
589 | } |
590 | #[inline] |
591 | pub fn italic(self) -> StyledObject<D> { |
592 | self.attr(Attribute::Italic) |
593 | } |
594 | #[inline] |
595 | pub fn underlined(self) -> StyledObject<D> { |
596 | self.attr(Attribute::Underlined) |
597 | } |
598 | #[inline] |
599 | pub fn blink(self) -> StyledObject<D> { |
600 | self.attr(Attribute::Blink) |
601 | } |
602 | #[inline] |
603 | pub fn blink_fast(self) -> StyledObject<D> { |
604 | self.attr(Attribute::BlinkFast) |
605 | } |
606 | #[inline] |
607 | pub fn reverse(self) -> StyledObject<D> { |
608 | self.attr(Attribute::Reverse) |
609 | } |
610 | #[inline] |
611 | pub fn hidden(self) -> StyledObject<D> { |
612 | self.attr(Attribute::Hidden) |
613 | } |
614 | #[inline] |
615 | pub fn strikethrough(self) -> StyledObject<D> { |
616 | self.attr(Attribute::StrikeThrough) |
617 | } |
618 | } |
619 | |
620 | macro_rules! impl_fmt { |
621 | ($name:ident) => { |
622 | impl<D: fmt::$name> fmt::$name for StyledObject<D> { |
623 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
624 | let mut reset = false; |
625 | if self |
626 | .style |
627 | .force |
628 | .unwrap_or_else(|| match self.style.for_stderr { |
629 | true => colors_enabled_stderr(), |
630 | false => colors_enabled(), |
631 | }) |
632 | { |
633 | if let Some(fg) = self.style.fg { |
634 | if fg.is_color256() { |
635 | write!(f, "\x1b [38;5;{}m", fg.ansi_num())?; |
636 | } else if self.style.fg_bright { |
637 | write!(f, "\x1b [38;5;{}m", fg.ansi_num() + 8)?; |
638 | } else { |
639 | write!(f, "\x1b [{}m", fg.ansi_num() + 30)?; |
640 | } |
641 | reset = true; |
642 | } |
643 | if let Some(bg) = self.style.bg { |
644 | if bg.is_color256() { |
645 | write!(f, "\x1b [48;5;{}m", bg.ansi_num())?; |
646 | } else if self.style.bg_bright { |
647 | write!(f, "\x1b [48;5;{}m", bg.ansi_num() + 8)?; |
648 | } else { |
649 | write!(f, "\x1b [{}m", bg.ansi_num() + 40)?; |
650 | } |
651 | reset = true; |
652 | } |
653 | for attr in &self.style.attrs { |
654 | write!(f, "\x1b [{}m", attr.ansi_num())?; |
655 | reset = true; |
656 | } |
657 | } |
658 | fmt::$name::fmt(&self.val, f)?; |
659 | if reset { |
660 | write!(f, "\x1b [0m")?; |
661 | } |
662 | Ok(()) |
663 | } |
664 | } |
665 | }; |
666 | } |
667 | |
668 | impl_fmt!(Binary); |
669 | impl_fmt!(Debug); |
670 | impl_fmt!(Display); |
671 | impl_fmt!(LowerExp); |
672 | impl_fmt!(LowerHex); |
673 | impl_fmt!(Octal); |
674 | impl_fmt!(Pointer); |
675 | impl_fmt!(UpperExp); |
676 | impl_fmt!(UpperHex); |
677 | |
678 | /// "Intelligent" emoji formatter. |
679 | /// |
680 | /// This struct intelligently wraps an emoji so that it is rendered |
681 | /// only on systems that want emojis and renders a fallback on others. |
682 | /// |
683 | /// Example: |
684 | /// |
685 | /// ```rust |
686 | /// use console::Emoji; |
687 | /// println!("[3/4] {}Downloading ...", Emoji( "🚚 ", "")); |
688 | /// println!("[4/4] {} Done!", Emoji( "✨", ":-)")); |
689 | /// ``` |
690 | #[derive(Copy, Clone)] |
691 | pub struct Emoji<'a, 'b>(pub &'a str, pub &'b str); |
692 | |
693 | impl<'a, 'b> Emoji<'a, 'b> { |
694 | pub fn new(emoji: &'a str, fallback: &'b str) -> Emoji<'a, 'b> { |
695 | Emoji(emoji, fallback) |
696 | } |
697 | } |
698 | |
699 | impl fmt::Display for Emoji<'_, '_> { |
700 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
701 | if wants_emoji() { |
702 | write!(f, "{} ", self.0) |
703 | } else { |
704 | write!(f, "{} ", self.1) |
705 | } |
706 | } |
707 | } |
708 | |
709 | fn str_width(s: &str) -> usize { |
710 | #[cfg(feature = "unicode-width")] |
711 | { |
712 | use unicode_width::UnicodeWidthStr; |
713 | s.width() |
714 | } |
715 | #[cfg(not(feature = "unicode-width"))] |
716 | { |
717 | s.chars().count() |
718 | } |
719 | } |
720 | |
721 | #[cfg(feature = "ansi-parsing")] |
722 | fn char_width(c: char) -> usize { |
723 | #[cfg(feature = "unicode-width")] |
724 | { |
725 | use unicode_width::UnicodeWidthChar; |
726 | c.width().unwrap_or(default:0) |
727 | } |
728 | #[cfg(not(feature = "unicode-width"))] |
729 | { |
730 | let _c = c; |
731 | 1 |
732 | } |
733 | } |
734 | |
735 | /// Truncates a string to a certain number of characters. |
736 | /// |
737 | /// This ensures that escape codes are not screwed up in the process. |
738 | /// If the maximum length is hit the string will be truncated but |
739 | /// escapes code will still be honored. If truncation takes place |
740 | /// the tail string will be appended. |
741 | pub fn truncate_str<'a>(s: &'a str, width: usize, tail: &str) -> Cow<'a, str> { |
742 | #[cfg(feature = "ansi-parsing")] |
743 | { |
744 | use std::cmp::Ordering; |
745 | let mut iter = AnsiCodeIterator::new(s); |
746 | let mut length = 0; |
747 | let mut rv = None; |
748 | |
749 | while let Some(item) = iter.next() { |
750 | match item { |
751 | (s, false) => { |
752 | if rv.is_none() { |
753 | if str_width(s) + length > width - str_width(tail) { |
754 | let ts = iter.current_slice(); |
755 | |
756 | let mut s_byte = 0; |
757 | let mut s_width = 0; |
758 | let rest_width = width - str_width(tail) - length; |
759 | for c in s.chars() { |
760 | s_byte += c.len_utf8(); |
761 | s_width += char_width(c); |
762 | match s_width.cmp(&rest_width) { |
763 | Ordering::Equal => break, |
764 | Ordering::Greater => { |
765 | s_byte -= c.len_utf8(); |
766 | break; |
767 | } |
768 | Ordering::Less => continue, |
769 | } |
770 | } |
771 | |
772 | let idx = ts.len() - s.len() + s_byte; |
773 | let mut buf = ts[..idx].to_string(); |
774 | buf.push_str(tail); |
775 | rv = Some(buf); |
776 | } |
777 | length += str_width(s); |
778 | } |
779 | } |
780 | (s, true) => { |
781 | if let Some(ref mut rv) = rv { |
782 | rv.push_str(s); |
783 | } |
784 | } |
785 | } |
786 | } |
787 | |
788 | if let Some(buf) = rv { |
789 | Cow::Owned(buf) |
790 | } else { |
791 | Cow::Borrowed(s) |
792 | } |
793 | } |
794 | |
795 | #[cfg(not(feature = "ansi-parsing"))] |
796 | { |
797 | if s.len() <= width - tail.len() { |
798 | Cow::Borrowed(s) |
799 | } else { |
800 | Cow::Owned(format!( |
801 | "{}{}", |
802 | s.get(..width - tail.len()).unwrap_or_default(), |
803 | tail |
804 | )) |
805 | } |
806 | } |
807 | } |
808 | |
809 | /// Pads a string to fill a certain number of characters. |
810 | /// |
811 | /// This will honor ansi codes correctly and allows you to align a string |
812 | /// on the left, right or centered. Additionally truncation can be enabled |
813 | /// by setting `truncate` to a string that should be used as a truncation |
814 | /// marker. |
815 | pub fn pad_str<'a>( |
816 | s: &'a str, |
817 | width: usize, |
818 | align: Alignment, |
819 | truncate: Option<&str>, |
820 | ) -> Cow<'a, str> { |
821 | pad_str_with(s, width, align, truncate, pad:' ') |
822 | } |
823 | /// Pads a string with specific padding to fill a certain number of characters. |
824 | /// |
825 | /// This will honor ansi codes correctly and allows you to align a string |
826 | /// on the left, right or centered. Additionally truncation can be enabled |
827 | /// by setting `truncate` to a string that should be used as a truncation |
828 | /// marker. |
829 | pub fn pad_str_with<'a>( |
830 | s: &'a str, |
831 | width: usize, |
832 | align: Alignment, |
833 | truncate: Option<&str>, |
834 | pad: char, |
835 | ) -> Cow<'a, str> { |
836 | let cols = measure_text_width(s); |
837 | |
838 | if cols >= width { |
839 | return match truncate { |
840 | None => Cow::Borrowed(s), |
841 | Some(tail) => truncate_str(s, width, tail), |
842 | }; |
843 | } |
844 | |
845 | let diff = width - cols; |
846 | |
847 | let (left_pad, right_pad) = match align { |
848 | Alignment::Left => (0, diff), |
849 | Alignment::Right => (diff, 0), |
850 | Alignment::Center => (diff / 2, diff - diff / 2), |
851 | }; |
852 | |
853 | let mut rv = String::new(); |
854 | for _ in 0..left_pad { |
855 | rv.push(pad); |
856 | } |
857 | rv.push_str(s); |
858 | for _ in 0..right_pad { |
859 | rv.push(pad); |
860 | } |
861 | Cow::Owned(rv) |
862 | } |
863 | |
864 | #[test] |
865 | fn test_text_width() { |
866 | let s = style("foo") |
867 | .red() |
868 | .on_black() |
869 | .bold() |
870 | .force_styling(true) |
871 | .to_string(); |
872 | assert_eq!( |
873 | measure_text_width(&s), |
874 | if cfg!(feature = "ansi-parsing") { |
875 | 3 |
876 | } else if cfg!(feature = "unicode-width") { |
877 | 17 |
878 | } else { |
879 | 21 |
880 | } |
881 | ); |
882 | } |
883 | |
884 | #[test] |
885 | #[cfg(all(feature = "unicode-width", feature = "ansi-parsing"))] |
886 | fn test_truncate_str() { |
887 | let s = format!("foo {}", style( "bar").red().force_styling(true)); |
888 | assert_eq!( |
889 | &truncate_str(&s, 5, ""), |
890 | &format!("foo {}", style( "b").red().force_styling(true)) |
891 | ); |
892 | let s = format!("foo {}", style( "bar").red().force_styling(true)); |
893 | assert_eq!( |
894 | &truncate_str(&s, 5, "!"), |
895 | &format!("foo {}", style( "!").red().force_styling(true)) |
896 | ); |
897 | let s = format!("foo {} baz", style( "bar").red().force_styling(true)); |
898 | assert_eq!( |
899 | &truncate_str(&s, 10, "..."), |
900 | &format!("foo {}...", style( "bar").red().force_styling(true)) |
901 | ); |
902 | let s = format!("foo {}", style( "バー").red().force_styling(true)); |
903 | assert_eq!( |
904 | &truncate_str(&s, 5, ""), |
905 | &format!("foo {}", style( "").red().force_styling(true)) |
906 | ); |
907 | let s = format!("foo {}", style( "バー").red().force_styling(true)); |
908 | assert_eq!( |
909 | &truncate_str(&s, 6, ""), |
910 | &format!("foo {}", style( "バ").red().force_styling(true)) |
911 | ); |
912 | } |
913 | |
914 | #[test] |
915 | fn test_truncate_str_no_ansi() { |
916 | assert_eq!(&truncate_str("foo bar", 5, ""), "foo b"); |
917 | assert_eq!(&truncate_str("foo bar", 5, "!"), "foo !"); |
918 | assert_eq!(&truncate_str("foo bar baz", 10, "..."), "foo bar..."); |
919 | } |
920 | |
921 | #[test] |
922 | fn test_pad_str() { |
923 | assert_eq!(pad_str("foo", 7, Alignment::Center, None), " foo "); |
924 | assert_eq!(pad_str("foo", 7, Alignment::Left, None), "foo "); |
925 | assert_eq!(pad_str("foo", 7, Alignment::Right, None), " foo"); |
926 | assert_eq!(pad_str("foo", 3, Alignment::Left, None), "foo"); |
927 | assert_eq!(pad_str("foobar", 3, Alignment::Left, None), "foobar"); |
928 | assert_eq!(pad_str("foobar", 3, Alignment::Left, Some( "")), "foo"); |
929 | assert_eq!( |
930 | pad_str("foobarbaz", 6, Alignment::Left, Some( "...")), |
931 | "foo..." |
932 | ); |
933 | } |
934 | |
935 | #[test] |
936 | fn test_pad_str_with() { |
937 | assert_eq!( |
938 | pad_str_with("foo", 7, Alignment::Center, None, '#'), |
939 | "##foo##" |
940 | ); |
941 | assert_eq!( |
942 | pad_str_with("foo", 7, Alignment::Left, None, '#'), |
943 | "foo####" |
944 | ); |
945 | assert_eq!( |
946 | pad_str_with("foo", 7, Alignment::Right, None, '#'), |
947 | "####foo" |
948 | ); |
949 | assert_eq!(pad_str_with("foo", 3, Alignment::Left, None, '#'), "foo"); |
950 | assert_eq!( |
951 | pad_str_with("foobar", 3, Alignment::Left, None, '#'), |
952 | "foobar" |
953 | ); |
954 | assert_eq!( |
955 | pad_str_with("foobar", 3, Alignment::Left, Some( ""), '#'), |
956 | "foo" |
957 | ); |
958 | assert_eq!( |
959 | pad_str_with("foobarbaz", 6, Alignment::Left, Some( "..."), '#'), |
960 | "foo..." |
961 | ); |
962 | } |
963 |
Definitions
- default_colors_enabled
- colors_enabled
- set_colors_enabled
- colors_enabled_stderr
- set_colors_enabled_stderr
- measure_text_width
- Color
- Black
- Red
- Green
- Yellow
- Blue
- Magenta
- Cyan
- White
- Color256
- ansi_num
- is_color256
- Attribute
- Bold
- Dim
- Italic
- Underlined
- Blink
- BlinkFast
- Reverse
- Hidden
- StrikeThrough
- ansi_num
- Alignment
- Left
- Center
- Right
- Style
- fg
- bg
- fg_bright
- bg_bright
- attrs
- force
- for_stderr
- default
- new
- from_dotted_str
- apply_to
- force_styling
- for_stderr
- for_stdout
- fg
- bg
- attr
- black
- red
- green
- yellow
- blue
- magenta
- cyan
- white
- color256
- bright
- on_black
- on_red
- on_green
- on_yellow
- on_blue
- on_magenta
- on_cyan
- on_white
- on_color256
- on_bright
- bold
- dim
- italic
- underlined
- blink
- blink_fast
- reverse
- hidden
- strikethrough
- style
- StyledObject
- style
- val
- force_styling
- for_stderr
- for_stdout
- fg
- bg
- attr
- black
- red
- green
- yellow
- blue
- magenta
- cyan
- white
- color256
- bright
- on_black
- on_red
- on_green
- on_yellow
- on_blue
- on_magenta
- on_cyan
- on_white
- on_color256
- on_bright
- bold
- dim
- italic
- underlined
- blink
- blink_fast
- reverse
- hidden
- strikethrough
- impl_fmt
- Emoji
- new
- fmt
- str_width
- char_width
- truncate_str
- pad_str
Learn Rust with the experts
Find out more