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 lazy_static::lazy_static; |
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 | lazy_static! { |
26 | static ref STDOUT_COLORS: AtomicBool = AtomicBool::new(default_colors_enabled(&Term::stdout())); |
27 | static ref STDERR_COLORS: AtomicBool = AtomicBool::new(default_colors_enabled(&Term::stderr())); |
28 | } |
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<'a, 'b> fmt::Display for Emoji<'a, 'b> { |
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(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 rv.is_some() { |
782 | rv.as_mut().unwrap().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: String = styleStyledObject<&str>(val:"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 | |