1use std::borrow::BorrowMut;
16use std::collections::HashMap;
17use std::fmt;
18use std::io;
19use std::io::Error;
20use std::io::Write;
21use std::mem;
22use std::ops::Range;
23use std::sync::Arc;
24
25use crossterm::queue;
26use crossterm::style::Attribute;
27use crossterm::style::Color;
28use crossterm::style::SetAttribute;
29use crossterm::style::SetBackgroundColor;
30use crossterm::style::SetForegroundColor;
31use itertools::Itertools as _;
32use jj_lib::config::ConfigGetError;
33use jj_lib::config::StackedConfig;
34use serde::de::Deserialize as _;
35use serde::de::Error as _;
36use serde::de::IntoDeserializer as _;
37
38pub trait Formatter: Write {
40 fn raw(&mut self) -> io::Result<Box<dyn Write + '_>>;
43
44 fn push_label(&mut self, label: &str) -> io::Result<()>;
45
46 fn pop_label(&mut self) -> io::Result<()>;
47}
48
49impl dyn Formatter + '_ {
50 pub fn labeled<S: AsRef<str>>(&mut self, label: S) -> LabeledWriter<&mut Self, S> {
51 LabeledWriter {
52 formatter: self,
53 label,
54 }
55 }
56
57 pub fn with_label<E: From<io::Error>>(
58 &mut self,
59 label: &str,
60 write_inner: impl FnOnce(&mut dyn Formatter) -> Result<(), E>,
61 ) -> Result<(), E> {
62 self.push_label(label)?;
63 write_inner(self).and(self.pop_label().map_err(Into::into))
66 }
67}
68
69pub struct LabeledWriter<T, S> {
72 formatter: T,
73 label: S,
74}
75
76impl<T, S> LabeledWriter<T, S> {
77 pub fn new(formatter: T, label: S) -> Self {
78 LabeledWriter { formatter, label }
79 }
80
81 pub fn with_heading<H>(self, heading: H) -> HeadingLabeledWriter<T, S, H> {
83 HeadingLabeledWriter::new(self, heading)
84 }
85}
86
87impl<'a, T, S> LabeledWriter<T, S>
88where
89 T: BorrowMut<dyn Formatter + 'a>,
90 S: AsRef<str>,
91{
92 pub fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> io::Result<()> {
93 self.with_labeled(|formatter| formatter.write_fmt(args))
94 }
95
96 fn with_labeled(
97 &mut self,
98 write_inner: impl FnOnce(&mut dyn Formatter) -> io::Result<()>,
99 ) -> io::Result<()> {
100 self.formatter
101 .borrow_mut()
102 .with_label(self.label.as_ref(), write_inner)
103 }
104}
105
106pub struct HeadingLabeledWriter<T, S, H> {
111 writer: LabeledWriter<T, S>,
112 heading: Option<H>,
113}
114
115impl<T, S, H> HeadingLabeledWriter<T, S, H> {
116 pub fn new(writer: LabeledWriter<T, S>, heading: H) -> Self {
117 HeadingLabeledWriter {
118 writer,
119 heading: Some(heading),
120 }
121 }
122}
123
124impl<'a, T, S, H> HeadingLabeledWriter<T, S, H>
125where
126 T: BorrowMut<dyn Formatter + 'a>,
127 S: AsRef<str>,
128 H: fmt::Display,
129{
130 pub fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> io::Result<()> {
131 self.writer.with_labeled(|formatter| {
132 if let Some(heading) = self.heading.take() {
133 write!(formatter.labeled("heading"), "{heading}")?;
134 }
135 formatter.write_fmt(args)
136 })
137 }
138}
139
140type Rules = Vec<(Vec<String>, Style)>;
141
142#[derive(Clone, Debug)]
144pub struct FormatterFactory {
145 kind: FormatterFactoryKind,
146}
147
148#[derive(Clone, Debug)]
149enum FormatterFactoryKind {
150 PlainText,
151 Sanitized,
152 Color { rules: Arc<Rules>, debug: bool },
153}
154
155impl FormatterFactory {
156 pub fn plain_text() -> Self {
157 let kind = FormatterFactoryKind::PlainText;
158 FormatterFactory { kind }
159 }
160
161 pub fn sanitized() -> Self {
162 let kind = FormatterFactoryKind::Sanitized;
163 FormatterFactory { kind }
164 }
165
166 pub fn color(config: &StackedConfig, debug: bool) -> Result<Self, ConfigGetError> {
167 let rules = Arc::new(rules_from_config(config)?);
168 let kind = FormatterFactoryKind::Color { rules, debug };
169 Ok(FormatterFactory { kind })
170 }
171
172 pub fn new_formatter<'output, W: Write + 'output>(
173 &self,
174 output: W,
175 ) -> Box<dyn Formatter + 'output> {
176 match &self.kind {
177 FormatterFactoryKind::PlainText => Box::new(PlainTextFormatter::new(output)),
178 FormatterFactoryKind::Sanitized => Box::new(SanitizingFormatter::new(output)),
179 FormatterFactoryKind::Color { rules, debug } => {
180 Box::new(ColorFormatter::new(output, rules.clone(), *debug))
181 }
182 }
183 }
184
185 pub fn is_color(&self) -> bool {
186 matches!(self.kind, FormatterFactoryKind::Color { .. })
187 }
188}
189
190pub struct PlainTextFormatter<W> {
191 output: W,
192}
193
194impl<W> PlainTextFormatter<W> {
195 pub fn new(output: W) -> PlainTextFormatter<W> {
196 Self { output }
197 }
198}
199
200impl<W: Write> Write for PlainTextFormatter<W> {
201 fn write(&mut self, data: &[u8]) -> Result<usize, Error> {
202 self.output.write(data)
203 }
204
205 fn flush(&mut self) -> Result<(), Error> {
206 self.output.flush()
207 }
208}
209
210impl<W: Write> Formatter for PlainTextFormatter<W> {
211 fn raw(&mut self) -> io::Result<Box<dyn Write + '_>> {
212 Ok(Box::new(self.output.by_ref()))
213 }
214
215 fn push_label(&mut self, _label: &str) -> io::Result<()> {
216 Ok(())
217 }
218
219 fn pop_label(&mut self) -> io::Result<()> {
220 Ok(())
221 }
222}
223
224pub struct SanitizingFormatter<W> {
225 output: W,
226}
227
228impl<W> SanitizingFormatter<W> {
229 pub fn new(output: W) -> SanitizingFormatter<W> {
230 Self { output }
231 }
232}
233
234impl<W: Write> Write for SanitizingFormatter<W> {
235 fn write(&mut self, data: &[u8]) -> Result<usize, Error> {
236 write_sanitized(&mut self.output, data)?;
237 Ok(data.len())
238 }
239
240 fn flush(&mut self) -> Result<(), Error> {
241 self.output.flush()
242 }
243}
244
245impl<W: Write> Formatter for SanitizingFormatter<W> {
246 fn raw(&mut self) -> io::Result<Box<dyn Write + '_>> {
247 Ok(Box::new(self.output.by_ref()))
248 }
249
250 fn push_label(&mut self, _label: &str) -> io::Result<()> {
251 Ok(())
252 }
253
254 fn pop_label(&mut self) -> io::Result<()> {
255 Ok(())
256 }
257}
258
259#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Deserialize)]
260#[serde(default, rename_all = "kebab-case")]
261pub struct Style {
262 #[serde(deserialize_with = "deserialize_color_opt")]
263 pub fg: Option<Color>,
264 #[serde(deserialize_with = "deserialize_color_opt")]
265 pub bg: Option<Color>,
266 pub bold: Option<bool>,
267 pub italic: Option<bool>,
268 pub underline: Option<bool>,
269 pub reverse: Option<bool>,
270}
271
272impl Style {
273 fn merge(&mut self, other: &Style) {
274 self.fg = other.fg.or(self.fg);
275 self.bg = other.bg.or(self.bg);
276 self.bold = other.bold.or(self.bold);
277 self.italic = other.italic.or(self.italic);
278 self.underline = other.underline.or(self.underline);
279 self.reverse = other.reverse.or(self.reverse);
280 }
281}
282
283#[derive(Clone, Debug)]
284pub struct ColorFormatter<W: Write> {
285 output: W,
286 rules: Arc<Rules>,
287 labels: Vec<String>,
290 cached_styles: HashMap<Vec<String>, Style>,
291 current_style: Style,
293 current_debug: Option<String>,
296}
297
298impl<W: Write> ColorFormatter<W> {
299 pub fn new(output: W, rules: Arc<Rules>, debug: bool) -> ColorFormatter<W> {
300 ColorFormatter {
301 output,
302 rules,
303 labels: vec![],
304 cached_styles: HashMap::new(),
305 current_style: Style::default(),
306 current_debug: debug.then(String::new),
307 }
308 }
309
310 pub fn for_config(
311 output: W,
312 config: &StackedConfig,
313 debug: bool,
314 ) -> Result<Self, ConfigGetError> {
315 let rules = rules_from_config(config)?;
316 Ok(Self::new(output, Arc::new(rules), debug))
317 }
318
319 fn requested_style(&mut self) -> Style {
320 if let Some(cached) = self.cached_styles.get(&self.labels) {
321 cached.clone()
322 } else {
323 let mut matched_styles = vec![];
329 for (labels, style) in self.rules.as_ref() {
330 let mut labels_iter = self.labels.iter().enumerate();
331 let mut matched_indices = vec![];
333 for required_label in labels {
334 for (label_index, label) in &mut labels_iter {
335 if label == required_label {
336 matched_indices.push(label_index);
337 break;
338 }
339 }
340 }
341 if matched_indices.len() == labels.len() {
342 matched_indices.reverse();
343 matched_styles.push((style, matched_indices));
344 }
345 }
346 matched_styles.sort_by_key(|(_, indices)| indices.clone());
347
348 let mut style = Style::default();
349 for (matched_style, _) in matched_styles {
350 style.merge(matched_style);
351 }
352 self.cached_styles
353 .insert(self.labels.clone(), style.clone());
354 style
355 }
356 }
357
358 fn write_new_style(&mut self) -> io::Result<()> {
359 let new_debug = match &self.current_debug {
360 Some(current) => {
361 let joined = self.labels.join(" ");
362 if joined == *current {
363 None
364 } else {
365 if !current.is_empty() {
366 write!(self.output, ">>")?;
367 }
368 Some(joined)
369 }
370 }
371 None => None,
372 };
373 let new_style = self.requested_style();
374 if new_style != self.current_style {
375 if new_style.bold != self.current_style.bold {
376 if new_style.bold.unwrap_or_default() {
377 queue!(self.output, SetAttribute(Attribute::Bold))?;
378 } else {
379 queue!(self.output, SetAttribute(Attribute::Reset))?;
384 self.current_style = Style::default();
385 }
386 }
387 if new_style.italic != self.current_style.italic {
388 if new_style.italic.unwrap_or_default() {
389 queue!(self.output, SetAttribute(Attribute::Italic))?;
390 } else {
391 queue!(self.output, SetAttribute(Attribute::NoItalic))?;
392 }
393 }
394 if new_style.underline != self.current_style.underline {
395 if new_style.underline.unwrap_or_default() {
396 queue!(self.output, SetAttribute(Attribute::Underlined))?;
397 } else {
398 queue!(self.output, SetAttribute(Attribute::NoUnderline))?;
399 }
400 }
401 if new_style.reverse != self.current_style.reverse {
402 if new_style.reverse.unwrap_or_default() {
403 queue!(self.output, SetAttribute(Attribute::Reverse))?;
404 } else {
405 queue!(self.output, SetAttribute(Attribute::NoReverse))?;
406 }
407 }
408 if new_style.fg != self.current_style.fg {
409 queue!(
410 self.output,
411 SetForegroundColor(new_style.fg.unwrap_or(Color::Reset))
412 )?;
413 }
414 if new_style.bg != self.current_style.bg {
415 queue!(
416 self.output,
417 SetBackgroundColor(new_style.bg.unwrap_or(Color::Reset))
418 )?;
419 }
420 self.current_style = new_style;
421 }
422 if let Some(d) = new_debug {
423 if !d.is_empty() {
424 write!(self.output, "<<{d}::")?;
425 }
426 self.current_debug = Some(d);
427 }
428 Ok(())
429 }
430}
431
432fn rules_from_config(config: &StackedConfig) -> Result<Rules, ConfigGetError> {
433 config
434 .table_keys("colors")
435 .map(|key| {
436 let labels = key
437 .split_whitespace()
438 .map(ToString::to_string)
439 .collect_vec();
440 let style = config.get_value_with(["colors", key], |value| {
441 if value.is_str() {
442 Ok(Style {
443 fg: Some(deserialize_color(value.into_deserializer())?),
444 bg: None,
445 bold: None,
446 italic: None,
447 underline: None,
448 reverse: None,
449 })
450 } else if value.is_inline_table() {
451 Style::deserialize(value.into_deserializer())
452 } else {
453 Err(toml_edit::de::Error::custom(format!(
454 "invalid type: {}, expected a color name or a table of styles",
455 value.type_name()
456 )))
457 }
458 })?;
459 Ok((labels, style))
460 })
461 .collect()
462}
463
464fn deserialize_color<'de, D>(deserializer: D) -> Result<Color, D::Error>
465where
466 D: serde::Deserializer<'de>,
467{
468 let name_or_hex = String::deserialize(deserializer)?;
469 color_for_name_or_hex(&name_or_hex).map_err(D::Error::custom)
470}
471
472fn deserialize_color_opt<'de, D>(deserializer: D) -> Result<Option<Color>, D::Error>
473where
474 D: serde::Deserializer<'de>,
475{
476 deserialize_color(deserializer).map(Some)
477}
478
479fn color_for_name_or_hex(name_or_hex: &str) -> Result<Color, String> {
480 match name_or_hex {
481 "default" => Ok(Color::Reset),
482 "black" => Ok(Color::Black),
483 "red" => Ok(Color::DarkRed),
484 "green" => Ok(Color::DarkGreen),
485 "yellow" => Ok(Color::DarkYellow),
486 "blue" => Ok(Color::DarkBlue),
487 "magenta" => Ok(Color::DarkMagenta),
488 "cyan" => Ok(Color::DarkCyan),
489 "white" => Ok(Color::Grey),
490 "bright black" => Ok(Color::DarkGrey),
491 "bright red" => Ok(Color::Red),
492 "bright green" => Ok(Color::Green),
493 "bright yellow" => Ok(Color::Yellow),
494 "bright blue" => Ok(Color::Blue),
495 "bright magenta" => Ok(Color::Magenta),
496 "bright cyan" => Ok(Color::Cyan),
497 "bright white" => Ok(Color::White),
498 _ => color_for_hex(name_or_hex).ok_or_else(|| format!("Invalid color: {name_or_hex}")),
499 }
500}
501
502fn color_for_hex(color: &str) -> Option<Color> {
503 if color.len() == 7
504 && color.starts_with('#')
505 && color[1..].chars().all(|c| c.is_ascii_hexdigit())
506 {
507 let r = u8::from_str_radix(&color[1..3], 16);
508 let g = u8::from_str_radix(&color[3..5], 16);
509 let b = u8::from_str_radix(&color[5..7], 16);
510 match (r, g, b) {
511 (Ok(r), Ok(g), Ok(b)) => Some(Color::Rgb { r, g, b }),
512 _ => None,
513 }
514 } else {
515 None
516 }
517}
518
519impl<W: Write> Write for ColorFormatter<W> {
520 fn write(&mut self, data: &[u8]) -> Result<usize, Error> {
521 for line in data.split_inclusive(|b| *b == b'\n') {
546 if line.ends_with(b"\n") {
547 self.write_new_style()?;
548 write_sanitized(&mut self.output, &line[..line.len() - 1])?;
549 let labels = mem::take(&mut self.labels);
550 self.write_new_style()?;
551 self.output.write_all(b"\n")?;
552 self.labels = labels;
553 } else {
554 self.write_new_style()?;
555 write_sanitized(&mut self.output, line)?;
556 }
557 }
558
559 Ok(data.len())
560 }
561
562 fn flush(&mut self) -> Result<(), Error> {
563 self.output.flush()
564 }
565}
566
567impl<W: Write> Formatter for ColorFormatter<W> {
568 fn raw(&mut self) -> io::Result<Box<dyn Write + '_>> {
569 self.write_new_style()?;
570 Ok(Box::new(self.output.by_ref()))
571 }
572
573 fn push_label(&mut self, label: &str) -> io::Result<()> {
574 self.labels.push(label.to_owned());
575 Ok(())
576 }
577
578 fn pop_label(&mut self) -> io::Result<()> {
579 self.labels.pop();
580 if self.labels.is_empty() {
581 self.write_new_style()?;
582 }
583 Ok(())
584 }
585}
586
587impl<W: Write> Drop for ColorFormatter<W> {
588 fn drop(&mut self) {
589 self.labels.clear();
592 self.write_new_style().ok();
593 }
594}
595
596#[derive(Clone, Debug, Default)]
604pub struct FormatRecorder {
605 data: Vec<u8>,
606 ops: Vec<(usize, FormatOp)>,
607}
608
609#[derive(Clone, Debug, Eq, PartialEq)]
610enum FormatOp {
611 PushLabel(String),
612 PopLabel,
613 RawEscapeSequence(Vec<u8>),
614}
615
616impl FormatRecorder {
617 pub fn new() -> Self {
618 FormatRecorder::default()
619 }
620
621 pub fn with_data(data: impl Into<Vec<u8>>) -> Self {
623 FormatRecorder {
624 data: data.into(),
625 ops: vec![],
626 }
627 }
628
629 pub fn data(&self) -> &[u8] {
630 &self.data
631 }
632
633 fn push_op(&mut self, op: FormatOp) {
634 self.ops.push((self.data.len(), op));
635 }
636
637 pub fn replay(&self, formatter: &mut dyn Formatter) -> io::Result<()> {
638 self.replay_with(formatter, |formatter, range| {
639 formatter.write_all(&self.data[range])
640 })
641 }
642
643 pub fn replay_with(
644 &self,
645 formatter: &mut dyn Formatter,
646 mut write_data: impl FnMut(&mut dyn Formatter, Range<usize>) -> io::Result<()>,
647 ) -> io::Result<()> {
648 let mut last_pos = 0;
649 let mut flush_data = |formatter: &mut dyn Formatter, pos| -> io::Result<()> {
650 if last_pos != pos {
651 write_data(formatter, last_pos..pos)?;
652 last_pos = pos;
653 }
654 Ok(())
655 };
656 for (pos, op) in &self.ops {
657 flush_data(formatter, *pos)?;
658 match op {
659 FormatOp::PushLabel(label) => formatter.push_label(label)?,
660 FormatOp::PopLabel => formatter.pop_label()?,
661 FormatOp::RawEscapeSequence(raw_escape_sequence) => {
662 formatter.raw()?.write_all(raw_escape_sequence)?;
663 }
664 }
665 }
666 flush_data(formatter, self.data.len())
667 }
668}
669
670impl Write for FormatRecorder {
671 fn write(&mut self, data: &[u8]) -> io::Result<usize> {
672 self.data.extend_from_slice(data);
673 Ok(data.len())
674 }
675
676 fn flush(&mut self) -> io::Result<()> {
677 Ok(())
678 }
679}
680
681struct RawEscapeSequenceRecorder<'a>(&'a mut FormatRecorder);
682
683impl Write for RawEscapeSequenceRecorder<'_> {
684 fn write(&mut self, data: &[u8]) -> io::Result<usize> {
685 self.0.push_op(FormatOp::RawEscapeSequence(data.to_vec()));
686 Ok(data.len())
687 }
688
689 fn flush(&mut self) -> io::Result<()> {
690 self.0.flush()
691 }
692}
693
694impl Formatter for FormatRecorder {
695 fn raw(&mut self) -> io::Result<Box<dyn Write + '_>> {
696 Ok(Box::new(RawEscapeSequenceRecorder(self)))
697 }
698
699 fn push_label(&mut self, label: &str) -> io::Result<()> {
700 self.push_op(FormatOp::PushLabel(label.to_owned()));
701 Ok(())
702 }
703
704 fn pop_label(&mut self) -> io::Result<()> {
705 self.push_op(FormatOp::PopLabel);
706 Ok(())
707 }
708}
709
710fn write_sanitized(output: &mut impl Write, buf: &[u8]) -> Result<(), Error> {
711 if buf.contains(&b'\x1b') {
712 let mut sanitized = Vec::with_capacity(buf.len());
713 for b in buf {
714 if *b == b'\x1b' {
715 sanitized.extend_from_slice("␛".as_bytes());
716 } else {
717 sanitized.push(*b);
718 }
719 }
720 output.write_all(&sanitized)
721 } else {
722 output.write_all(buf)
723 }
724}
725
726#[cfg(test)]
727mod tests {
728 use std::error::Error as _;
729 use std::str;
730
731 use bstr::BString;
732 use indexmap::IndexMap;
733 use indoc::indoc;
734 use jj_lib::config::ConfigLayer;
735 use jj_lib::config::ConfigSource;
736 use jj_lib::config::StackedConfig;
737
738 use super::*;
739
740 fn config_from_string(text: &str) -> StackedConfig {
741 let mut config = StackedConfig::empty();
742 config.add_layer(ConfigLayer::parse(ConfigSource::User, text).unwrap());
743 config
744 }
745
746 fn to_snapshot_string(output: impl Into<Vec<u8>>) -> BString {
750 let mut output = output.into();
751 output.extend_from_slice(b"[EOF]\n");
752 BString::new(output)
753 }
754
755 #[test]
756 fn test_plaintext_formatter() {
757 let mut output: Vec<u8> = vec![];
759 let mut formatter = PlainTextFormatter::new(&mut output);
760 formatter.push_label("warning").unwrap();
761 write!(formatter, "hello").unwrap();
762 formatter.pop_label().unwrap();
763 insta::assert_snapshot!(to_snapshot_string(output), @"hello[EOF]");
764 }
765
766 #[test]
767 fn test_plaintext_formatter_ansi_codes_in_text() {
768 let mut output: Vec<u8> = vec![];
770 let mut formatter = PlainTextFormatter::new(&mut output);
771 write!(formatter, "\x1b[1mactually bold\x1b[0m").unwrap();
772 insta::assert_snapshot!(to_snapshot_string(output), @"[1mactually bold[0m[EOF]");
773 }
774
775 #[test]
776 fn test_sanitizing_formatter_ansi_codes_in_text() {
777 let mut output: Vec<u8> = vec![];
779 let mut formatter = SanitizingFormatter::new(&mut output);
780 write!(formatter, "\x1b[1mnot actually bold\x1b[0m").unwrap();
781 insta::assert_snapshot!(to_snapshot_string(output), @"␛[1mnot actually bold␛[0m[EOF]");
782 }
783
784 #[test]
785 fn test_color_formatter_color_codes() {
786 let config = config_from_string(indoc! {"
789 [colors]
790 black = 'black'
791 red = 'red'
792 green = 'green'
793 yellow = 'yellow'
794 blue = 'blue'
795 magenta = 'magenta'
796 cyan = 'cyan'
797 white = 'white'
798 bright-black = 'bright black'
799 bright-red = 'bright red'
800 bright-green = 'bright green'
801 bright-yellow = 'bright yellow'
802 bright-blue = 'bright blue'
803 bright-magenta = 'bright magenta'
804 bright-cyan = 'bright cyan'
805 bright-white = 'bright white'
806 "});
807 let colors: IndexMap<String, String> = config.get("colors").unwrap();
808 let mut output: Vec<u8> = vec![];
809 let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap();
810 for (label, color) in &colors {
811 formatter.push_label(label).unwrap();
812 write!(formatter, " {color} ").unwrap();
813 formatter.pop_label().unwrap();
814 writeln!(formatter).unwrap();
815 }
816 drop(formatter);
817 insta::assert_snapshot!(to_snapshot_string(output), @r"
818 [38;5;0m black [39m
819 [38;5;1m red [39m
820 [38;5;2m green [39m
821 [38;5;3m yellow [39m
822 [38;5;4m blue [39m
823 [38;5;5m magenta [39m
824 [38;5;6m cyan [39m
825 [38;5;7m white [39m
826 [38;5;8m bright black [39m
827 [38;5;9m bright red [39m
828 [38;5;10m bright green [39m
829 [38;5;11m bright yellow [39m
830 [38;5;12m bright blue [39m
831 [38;5;13m bright magenta [39m
832 [38;5;14m bright cyan [39m
833 [38;5;15m bright white [39m
834 [EOF]
835 ");
836 }
837
838 #[test]
839 fn test_color_formatter_hex_colors() {
840 let config = config_from_string(indoc! {"
842 [colors]
843 black = '#000000'
844 white = '#ffffff'
845 pastel-blue = '#AFE0D9'
846 "});
847 let colors: IndexMap<String, String> = config.get("colors").unwrap();
848 let mut output: Vec<u8> = vec![];
849 let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap();
850 for label in colors.keys() {
851 formatter.push_label(&label.replace(' ', "-")).unwrap();
852 write!(formatter, " {label} ").unwrap();
853 formatter.pop_label().unwrap();
854 writeln!(formatter).unwrap();
855 }
856 drop(formatter);
857 insta::assert_snapshot!(to_snapshot_string(output), @r"
858 [38;2;0;0;0m black [39m
859 [38;2;255;255;255m white [39m
860 [38;2;175;224;217m pastel-blue [39m
861 [EOF]
862 ");
863 }
864
865 #[test]
866 fn test_color_formatter_single_label() {
867 let config = config_from_string(
870 r#"
871 colors.inside = "green"
872 "#,
873 );
874 let mut output: Vec<u8> = vec![];
875 let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap();
876 write!(formatter, " before ").unwrap();
877 formatter.push_label("inside").unwrap();
878 write!(formatter, " inside ").unwrap();
879 formatter.pop_label().unwrap();
880 write!(formatter, " after ").unwrap();
881 drop(formatter);
882 insta::assert_snapshot!(
883 to_snapshot_string(output), @" before [38;5;2m inside [39m after [EOF]");
884 }
885
886 #[test]
887 fn test_color_formatter_attributes() {
888 let config = config_from_string(
891 r#"
892 colors.red_fg = { fg = "red" }
893 colors.blue_bg = { bg = "blue" }
894 colors.bold_font = { bold = true }
895 colors.italic_text = { italic = true }
896 colors.underlined_text = { underline = true }
897 colors.reversed_colors = { reverse = true }
898 colors.multiple = { fg = "green", bg = "yellow", bold = true, italic = true, underline = true, reverse = true }
899 "#,
900 );
901 let mut output: Vec<u8> = vec![];
902 let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap();
903 formatter.push_label("red_fg").unwrap();
904 write!(formatter, " fg only ").unwrap();
905 formatter.pop_label().unwrap();
906 writeln!(formatter).unwrap();
907 formatter.push_label("blue_bg").unwrap();
908 write!(formatter, " bg only ").unwrap();
909 formatter.pop_label().unwrap();
910 writeln!(formatter).unwrap();
911 formatter.push_label("bold_font").unwrap();
912 write!(formatter, " bold only ").unwrap();
913 formatter.pop_label().unwrap();
914 writeln!(formatter).unwrap();
915 formatter.push_label("italic_text").unwrap();
916 write!(formatter, " italic only ").unwrap();
917 formatter.pop_label().unwrap();
918 writeln!(formatter).unwrap();
919 formatter.push_label("underlined_text").unwrap();
920 write!(formatter, " underlined only ").unwrap();
921 formatter.pop_label().unwrap();
922 writeln!(formatter).unwrap();
923 formatter.push_label("reversed_colors").unwrap();
924 write!(formatter, " reverse only ").unwrap();
925 formatter.pop_label().unwrap();
926 writeln!(formatter).unwrap();
927 formatter.push_label("multiple").unwrap();
928 write!(formatter, " single rule ").unwrap();
929 formatter.pop_label().unwrap();
930 writeln!(formatter).unwrap();
931 formatter.push_label("red_fg").unwrap();
932 formatter.push_label("blue_bg").unwrap();
933 write!(formatter, " two rules ").unwrap();
934 formatter.pop_label().unwrap();
935 formatter.pop_label().unwrap();
936 writeln!(formatter).unwrap();
937 drop(formatter);
938 insta::assert_snapshot!(to_snapshot_string(output), @r"
939 [38;5;1m fg only [39m
940 [48;5;4m bg only [49m
941 [1m bold only [0m
942 [3m italic only [23m
943 [4m underlined only [24m
944 [7m reverse only [27m
945 [1m[3m[4m[7m[38;5;2m[48;5;3m single rule [0m
946 [38;5;1m[48;5;4m two rules [39m[49m
947 [EOF]
948 ");
949 }
950
951 #[test]
952 fn test_color_formatter_bold_reset() {
953 let config = config_from_string(
955 r#"
956 colors.not_bold = { fg = "red", bg = "blue", italic = true, underline = true }
957 colors.bold_font = { bold = true }
958 "#,
959 );
960 let mut output: Vec<u8> = vec![];
961 let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap();
962 formatter.push_label("not_bold").unwrap();
963 write!(formatter, " not bold ").unwrap();
964 formatter.push_label("bold_font").unwrap();
965 write!(formatter, " bold ").unwrap();
966 formatter.pop_label().unwrap();
967 write!(formatter, " not bold again ").unwrap();
968 formatter.pop_label().unwrap();
969 drop(formatter);
970 insta::assert_snapshot!(
971 to_snapshot_string(output),
972 @"[3m[4m[38;5;1m[48;5;4m not bold [1m bold [0m[3m[4m[38;5;1m[48;5;4m not bold again [23m[24m[39m[49m[EOF]");
973 }
974
975 #[test]
976 fn test_color_formatter_no_space() {
977 let config = config_from_string(
979 r#"
980 colors.red = "red"
981 colors.green = "green"
982 "#,
983 );
984 let mut output: Vec<u8> = vec![];
985 let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap();
986 write!(formatter, "before").unwrap();
987 formatter.push_label("red").unwrap();
988 write!(formatter, "first").unwrap();
989 formatter.pop_label().unwrap();
990 formatter.push_label("green").unwrap();
991 write!(formatter, "second").unwrap();
992 formatter.pop_label().unwrap();
993 write!(formatter, "after").unwrap();
994 drop(formatter);
995 insta::assert_snapshot!(
996 to_snapshot_string(output), @"before[38;5;1mfirst[39m[38;5;2msecond[39mafter[EOF]");
997 }
998
999 #[test]
1000 fn test_color_formatter_ansi_codes_in_text() {
1001 let config = config_from_string(
1003 r#"
1004 colors.red = "red"
1005 "#,
1006 );
1007 let mut output: Vec<u8> = vec![];
1008 let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap();
1009 formatter.push_label("red").unwrap();
1010 write!(formatter, "\x1b[1mnot actually bold\x1b[0m").unwrap();
1011 formatter.pop_label().unwrap();
1012 drop(formatter);
1013 insta::assert_snapshot!(
1014 to_snapshot_string(output), @"[38;5;1m␛[1mnot actually bold␛[0m[39m[EOF]");
1015 }
1016
1017 #[test]
1018 fn test_color_formatter_nested() {
1019 let config = config_from_string(
1023 r#"
1024 colors.outer = "blue"
1025 colors.inner = "red"
1026 colors."outer inner" = "green"
1027 "#,
1028 );
1029 let mut output: Vec<u8> = vec![];
1030 let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap();
1031 write!(formatter, " before outer ").unwrap();
1032 formatter.push_label("outer").unwrap();
1033 write!(formatter, " before inner ").unwrap();
1034 formatter.push_label("inner").unwrap();
1035 write!(formatter, " inside inner ").unwrap();
1036 formatter.pop_label().unwrap();
1037 write!(formatter, " after inner ").unwrap();
1038 formatter.pop_label().unwrap();
1039 write!(formatter, " after outer ").unwrap();
1040 drop(formatter);
1041 insta::assert_snapshot!(
1042 to_snapshot_string(output),
1043 @" before outer [38;5;4m before inner [38;5;2m inside inner [38;5;4m after inner [39m after outer [EOF]");
1044 }
1045
1046 #[test]
1047 fn test_color_formatter_partial_match() {
1048 let config = config_from_string(
1050 r#"
1051 colors."outer inner" = "green"
1052 "#,
1053 );
1054 let mut output: Vec<u8> = vec![];
1055 let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap();
1056 formatter.push_label("outer").unwrap();
1057 write!(formatter, " not colored ").unwrap();
1058 formatter.push_label("inner").unwrap();
1059 write!(formatter, " colored ").unwrap();
1060 formatter.pop_label().unwrap();
1061 write!(formatter, " not colored ").unwrap();
1062 formatter.pop_label().unwrap();
1063 drop(formatter);
1064 insta::assert_snapshot!(
1065 to_snapshot_string(output),
1066 @" not colored [38;5;2m colored [39m not colored [EOF]");
1067 }
1068
1069 #[test]
1070 fn test_color_formatter_unrecognized_color() {
1071 let config = config_from_string(
1073 r#"
1074 colors."outer" = "red"
1075 colors."outer inner" = "bloo"
1076 "#,
1077 );
1078 let mut output: Vec<u8> = vec![];
1079 let err = ColorFormatter::for_config(&mut output, &config, false).unwrap_err();
1080 insta::assert_snapshot!(err, @r#"Invalid type or value for colors."outer inner""#);
1081 insta::assert_snapshot!(err.source().unwrap(), @"Invalid color: bloo");
1082 }
1083
1084 #[test]
1085 fn test_color_formatter_unrecognized_hex_color() {
1086 let config = config_from_string(
1088 r##"
1089 colors."outer" = "red"
1090 colors."outer inner" = "#ffgggg"
1091 "##,
1092 );
1093 let mut output: Vec<u8> = vec![];
1094 let err = ColorFormatter::for_config(&mut output, &config, false).unwrap_err();
1095 insta::assert_snapshot!(err, @r#"Invalid type or value for colors."outer inner""#);
1096 insta::assert_snapshot!(err.source().unwrap(), @"Invalid color: #ffgggg");
1097 }
1098
1099 #[test]
1100 fn test_color_formatter_invalid_type_of_color() {
1101 let config = config_from_string("colors.foo = []");
1102 let err = ColorFormatter::for_config(&mut Vec::new(), &config, false).unwrap_err();
1103 insta::assert_snapshot!(err, @"Invalid type or value for colors.foo");
1104 insta::assert_snapshot!(
1105 err.source().unwrap(),
1106 @"invalid type: array, expected a color name or a table of styles");
1107 }
1108
1109 #[test]
1110 fn test_color_formatter_invalid_type_of_style() {
1111 let config = config_from_string("colors.foo = { bold = 1 }");
1112 let err = ColorFormatter::for_config(&mut Vec::new(), &config, false).unwrap_err();
1113 insta::assert_snapshot!(err, @"Invalid type or value for colors.foo");
1114 insta::assert_snapshot!(err.source().unwrap(), @r"
1115 invalid type: integer `1`, expected a boolean
1116 in `bold`
1117 ");
1118 }
1119
1120 #[test]
1121 fn test_color_formatter_normal_color() {
1122 let config = config_from_string(
1125 r#"
1126 colors."outer" = {bg="yellow", fg="blue"}
1127 colors."outer default_fg" = "default"
1128 colors."outer default_bg" = {bg = "default"}
1129 "#,
1130 );
1131 let mut output: Vec<u8> = vec![];
1132 let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap();
1133 formatter.push_label("outer").unwrap();
1134 write!(formatter, "Blue on yellow, ").unwrap();
1135 formatter.push_label("default_fg").unwrap();
1136 write!(formatter, " default fg, ").unwrap();
1137 formatter.pop_label().unwrap();
1138 write!(formatter, " and back.\nBlue on yellow, ").unwrap();
1139 formatter.push_label("default_bg").unwrap();
1140 write!(formatter, " default bg, ").unwrap();
1141 formatter.pop_label().unwrap();
1142 write!(formatter, " and back.").unwrap();
1143 drop(formatter);
1144 insta::assert_snapshot!(to_snapshot_string(output), @r"
1145 [38;5;4m[48;5;3mBlue on yellow, [39m default fg, [38;5;4m and back.[39m[49m
1146 [38;5;4m[48;5;3mBlue on yellow, [49m default bg, [48;5;3m and back.[39m[49m[EOF]
1147 ");
1148 }
1149
1150 #[test]
1151 fn test_color_formatter_sibling() {
1152 let config = config_from_string(
1154 r#"
1155 colors."outer1 inner1" = "red"
1156 colors.inner2 = "green"
1157 "#,
1158 );
1159 let mut output: Vec<u8> = vec![];
1160 let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap();
1161 formatter.push_label("outer1").unwrap();
1162 formatter.push_label("inner2").unwrap();
1163 write!(formatter, " hello ").unwrap();
1164 formatter.pop_label().unwrap();
1165 formatter.pop_label().unwrap();
1166 drop(formatter);
1167 insta::assert_snapshot!(to_snapshot_string(output), @"[38;5;2m hello [39m[EOF]");
1168 }
1169
1170 #[test]
1171 fn test_color_formatter_reverse_order() {
1172 let config = config_from_string(
1174 r#"
1175 colors."inner outer" = "green"
1176 "#,
1177 );
1178 let mut output: Vec<u8> = vec![];
1179 let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap();
1180 formatter.push_label("outer").unwrap();
1181 formatter.push_label("inner").unwrap();
1182 write!(formatter, " hello ").unwrap();
1183 formatter.pop_label().unwrap();
1184 formatter.pop_label().unwrap();
1185 drop(formatter);
1186 insta::assert_snapshot!(to_snapshot_string(output), @" hello [EOF]");
1187 }
1188
1189 #[test]
1190 fn test_color_formatter_innermost_wins() {
1191 let config = config_from_string(
1193 r#"
1194 colors."a" = "red"
1195 colors."b" = "green"
1196 colors."a c" = "blue"
1197 colors."b c" = "yellow"
1198 "#,
1199 );
1200 let mut output: Vec<u8> = vec![];
1201 let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap();
1202 formatter.push_label("a").unwrap();
1203 write!(formatter, " a1 ").unwrap();
1204 formatter.push_label("b").unwrap();
1205 write!(formatter, " b1 ").unwrap();
1206 formatter.push_label("c").unwrap();
1207 write!(formatter, " c ").unwrap();
1208 formatter.pop_label().unwrap();
1209 write!(formatter, " b2 ").unwrap();
1210 formatter.pop_label().unwrap();
1211 write!(formatter, " a2 ").unwrap();
1212 formatter.pop_label().unwrap();
1213 drop(formatter);
1214 insta::assert_snapshot!(
1215 to_snapshot_string(output),
1216 @"[38;5;1m a1 [38;5;2m b1 [38;5;3m c [38;5;2m b2 [38;5;1m a2 [39m[EOF]");
1217 }
1218
1219 #[test]
1220 fn test_color_formatter_dropped() {
1221 let config = config_from_string(
1224 r#"
1225 colors.outer = "green"
1226 "#,
1227 );
1228 let mut output: Vec<u8> = vec![];
1229 let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap();
1230 formatter.push_label("outer").unwrap();
1231 formatter.push_label("inner").unwrap();
1232 write!(formatter, " inside ").unwrap();
1233 drop(formatter);
1234 insta::assert_snapshot!(to_snapshot_string(output), @"[38;5;2m inside [39m[EOF]");
1235 }
1236
1237 #[test]
1238 fn test_color_formatter_debug() {
1239 let config = config_from_string(
1242 r#"
1243 colors.outer = "green"
1244 "#,
1245 );
1246 let mut output: Vec<u8> = vec![];
1247 let mut formatter = ColorFormatter::for_config(&mut output, &config, true).unwrap();
1248 formatter.push_label("outer").unwrap();
1249 formatter.push_label("inner").unwrap();
1250 write!(formatter, " inside ").unwrap();
1251 formatter.pop_label().unwrap();
1252 formatter.pop_label().unwrap();
1253 drop(formatter);
1254 insta::assert_snapshot!(
1255 to_snapshot_string(output), @"[38;5;2m<<outer inner:: inside >>[39m[EOF]");
1256 }
1257
1258 #[test]
1259 fn test_heading_labeled_writer() {
1260 let config = config_from_string(
1261 r#"
1262 colors.inner = "green"
1263 colors."inner heading" = "red"
1264 "#,
1265 );
1266 let mut output: Vec<u8> = vec![];
1267 let mut formatter: Box<dyn Formatter> =
1268 Box::new(ColorFormatter::for_config(&mut output, &config, false).unwrap());
1269 formatter
1270 .as_mut()
1271 .labeled("inner")
1272 .with_heading("Should be noop: ");
1273 let mut writer = formatter
1274 .as_mut()
1275 .labeled("inner")
1276 .with_heading("Heading: ");
1277 write!(writer, "Message").unwrap();
1278 writeln!(writer, " continues").unwrap();
1279 drop(formatter);
1280 insta::assert_snapshot!(to_snapshot_string(output), @r"
1281 [38;5;1mHeading: [38;5;2mMessage[39m[38;5;2m continues[39m
1282 [EOF]
1283 ");
1284 }
1285
1286 #[test]
1287 fn test_heading_labeled_writer_empty_string() {
1288 let mut output: Vec<u8> = vec![];
1289 let mut formatter: Box<dyn Formatter> = Box::new(PlainTextFormatter::new(&mut output));
1290 let mut writer = formatter
1291 .as_mut()
1292 .labeled("inner")
1293 .with_heading("Heading: ");
1294 write!(writer, "").unwrap();
1297 write!(writer, "").unwrap();
1298 drop(formatter);
1299 insta::assert_snapshot!(to_snapshot_string(output), @"Heading: [EOF]");
1300 }
1301
1302 #[test]
1303 fn test_format_recorder() {
1304 let mut recorder = FormatRecorder::new();
1305 write!(recorder, " outer1 ").unwrap();
1306 recorder.push_label("inner").unwrap();
1307 write!(recorder, " inner1 ").unwrap();
1308 write!(recorder, " inner2 ").unwrap();
1309 recorder.pop_label().unwrap();
1310 write!(recorder, " outer2 ").unwrap();
1311
1312 insta::assert_snapshot!(
1313 to_snapshot_string(recorder.data()),
1314 @" outer1 inner1 inner2 outer2 [EOF]");
1315
1316 let config = config_from_string(r#" colors.inner = "red" "#);
1318 let mut output: Vec<u8> = vec![];
1319 let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap();
1320 recorder.replay(&mut formatter).unwrap();
1321 drop(formatter);
1322 insta::assert_snapshot!(
1323 to_snapshot_string(output),
1324 @" outer1 [38;5;1m inner1 inner2 [39m outer2 [EOF]");
1325
1326 let mut output: Vec<u8> = vec![];
1328 let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap();
1329 recorder
1330 .replay_with(&mut formatter, |formatter, range| {
1331 let data = &recorder.data()[range];
1332 write!(formatter, "<<{}>>", str::from_utf8(data).unwrap())
1333 })
1334 .unwrap();
1335 drop(formatter);
1336 insta::assert_snapshot!(
1337 to_snapshot_string(output),
1338 @"<< outer1 >>[38;5;1m<< inner1 inner2 >>[39m<< outer2 >>[EOF]");
1339 }
1340
1341 #[test]
1342 fn test_raw_format_recorder() {
1343 let mut recorder = FormatRecorder::new();
1345 write!(recorder.raw().unwrap(), " outer1 ").unwrap();
1346 recorder.push_label("inner").unwrap();
1347 write!(recorder.raw().unwrap(), " inner1 ").unwrap();
1348 write!(recorder.raw().unwrap(), " inner2 ").unwrap();
1349 recorder.pop_label().unwrap();
1350 write!(recorder.raw().unwrap(), " outer2 ").unwrap();
1351
1352 let config = config_from_string(r#" colors.inner = "red" "#);
1354 let mut output: Vec<u8> = vec![];
1355 let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap();
1356 recorder.replay(&mut formatter).unwrap();
1357 drop(formatter);
1358 insta::assert_snapshot!(
1359 to_snapshot_string(output), @" outer1 [38;5;1m inner1 inner2 [39m outer2 [EOF]");
1360
1361 let mut output: Vec<u8> = vec![];
1362 let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap();
1363 recorder
1364 .replay_with(&mut formatter, |_formatter, range| {
1365 panic!(
1366 "Called with {:?} when all output should be raw",
1367 str::from_utf8(&recorder.data()[range]).unwrap()
1368 );
1369 })
1370 .unwrap();
1371 drop(formatter);
1372 insta::assert_snapshot!(
1373 to_snapshot_string(output), @" outer1 [38;5;1m inner1 inner2 [39m outer2 [EOF]");
1374 }
1375}