just playing with tangled
at ig/vimdiffwarn 1375 lines 49 kB view raw
1// Copyright 2020 The Jujutsu Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// https://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15use 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 38// Lets the caller label strings and translates the labels to colors 39pub trait Formatter: Write { 40 /// Returns the backing `Write`. This is useful for writing data that is 41 /// already formatted, such as in the graphical log. 42 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 // Call `pop_label()` whether or not `write_inner()` fails, but don't let 64 // its error replace the one from `write_inner()`. 65 write_inner(self).and(self.pop_label().map_err(Into::into)) 66 } 67} 68 69/// `Formatter` wrapper to write a labeled message with `write!()` or 70/// `writeln!()`. 71pub 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 /// Turns into writer that prints labeled message with the `heading`. 82 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 106/// Like `LabeledWriter`, but also prints the `heading` once. 107/// 108/// The `heading` will be printed within the first `write!()` or `writeln!()` 109/// invocation, which is handy because `io::Error` can be handled there. 110pub 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/// Creates `Formatter` instances with preconfigured parameters. 143#[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 /// The stack of currently applied labels. These determine the desired 288 /// style. 289 labels: Vec<String>, 290 cached_styles: HashMap<Vec<String>, Style>, 291 /// The style we last wrote to the output. 292 current_style: Style, 293 /// The debug string (space-separated labels) we last wrote to the output. 294 /// Initialize to None to turn debug strings off. 295 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 // We use the reverse list of matched indices as a measure of how well the rule 324 // matches the actual labels. For example, for rule "a d" and the actual labels 325 // "a b c d", we'll get [3,0]. We compare them by Rust's default Vec comparison. 326 // That means "a d" will trump both rule "d" (priority [3]) and rule 327 // "a b c" (priority [2,1,0]). 328 let mut matched_styles = vec![]; 329 for (labels, style) in self.rules.as_ref() { 330 let mut labels_iter = self.labels.iter().enumerate(); 331 // The indexes in the current label stack that match the required label. 332 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 // NoBold results in double underlining on some terminals, so we use reset 380 // instead. However, that resets other attributes as well, so we reset 381 // our record of the current style so we re-apply the other attributes 382 // below. 383 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 /* 522 We clear the current style at the end of each line, and then we re-apply the style 523 after the newline. There are several reasons for this: 524 525 * We can more easily skip styling a trailing blank line, which other 526 internal code then can correctly detect as having a trailing 527 newline. 528 529 * Some tools (like `less -R`) add an extra newline if the final 530 character is not a newline (e.g. if there's a color reset after 531 it), which led to an annoying blank line after the diff summary in 532 e.g. `jj status`. 533 534 * Since each line is styled independently, you get all the necessary 535 escapes even when grepping through the output. 536 537 * Some terminals extend background color to the end of the terminal 538 (i.e. past the newline character), which is probably not what the 539 user wanted. 540 541 * Some tools (like `less -R`) get confused and lose coloring of lines 542 after a newline. 543 */ 544 545 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 // If a `ColorFormatter` was dropped without popping all labels first (perhaps 590 // because of an error), let's still try to reset any currently active style. 591 self.labels.clear(); 592 self.write_new_style().ok(); 593 } 594} 595 596/// Like buffered formatter, but records `push`/`pop_label()` calls. 597/// 598/// This allows you to manipulate the recorded data without losing labels. 599/// The recorded data and labels can be written to another formatter. If 600/// the destination formatter has already been labeled, the recorded labels 601/// will be stacked on top of the existing labels, and the subsequent data 602/// may be colorized differently. 603#[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 /// Creates new buffer containing the given `data`. 622 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 /// Appends "[EOF]" marker to the output text. 747 /// 748 /// This is a workaround for https://github.com/mitsuhiko/insta/issues/384. 749 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 // Test that PlainTextFormatter ignores labels. 758 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 // Test that ANSI codes in the input text are NOT escaped. 769 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), @"actually bold[EOF]"); 773 } 774 775 #[test] 776 fn test_sanitizing_formatter_ansi_codes_in_text() { 777 // Test that ANSI codes in the input text are escaped. 778 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 // Test the color code for each color. 787 // Use the color name as the label. 788 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  black  819  red  820  green  821  yellow  822  blue  823  magenta  824  cyan  825  white  826  bright black  827  bright red  828  bright green  829  bright yellow  830  bright blue  831  bright magenta  832  bright cyan  833  bright white  834 [EOF] 835 "); 836 } 837 838 #[test] 839 fn test_color_formatter_hex_colors() { 840 // Test the color code for each color. 841 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  black  859  white  860  pastel-blue  861 [EOF] 862 "); 863 } 864 865 #[test] 866 fn test_color_formatter_single_label() { 867 // Test that a single label can be colored and that the color is reset 868 // afterwards. 869 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  inside  after [EOF]"); 884 } 885 886 #[test] 887 fn test_color_formatter_attributes() { 888 // Test that each attribute of the style can be set and that they can be 889 // combined in a single rule or by using multiple rules. 890 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  fg only  940  bg only  941  bold only  942  italic only  943  underlined only  944  reverse only  945  single rule  946  two rules  947 [EOF] 948 "); 949 } 950 951 #[test] 952 fn test_color_formatter_bold_reset() { 953 // Test that we don't lose other attributes when we reset the bold attribute. 954 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 @" not bold  bold  not bold again [EOF]"); 973 } 974 975 #[test] 976 fn test_color_formatter_no_space() { 977 // Test that two different colors can touch. 978 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), @"beforefirstsecondafter[EOF]"); 997 } 998 999 #[test] 1000 fn test_color_formatter_ansi_codes_in_text() { 1001 // Test that ANSI codes in the input text are escaped. 1002 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), @"␛[1mnot actually bold␛[0m[EOF]"); 1015 } 1016 1017 #[test] 1018 fn test_color_formatter_nested() { 1019 // A color can be associated with a combination of labels. A more specific match 1020 // overrides a less specific match. After the inner label is removed, the outer 1021 // color is used again (we don't reset). 1022 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  before inner  inside inner  after inner  after outer [EOF]"); 1044 } 1045 1046 #[test] 1047 fn test_color_formatter_partial_match() { 1048 // A partial match doesn't count 1049 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  colored  not colored [EOF]"); 1067 } 1068 1069 #[test] 1070 fn test_color_formatter_unrecognized_color() { 1071 // An unrecognized color causes an error. 1072 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 // An unrecognized hex color causes an error. 1087 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 // The "default" color resets the color. It is possible to reset only the 1123 // background or only the foreground. 1124 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 Blue on yellow,  default fg,  and back. 1146 Blue on yellow,  default bg,  and back.[EOF] 1147 "); 1148 } 1149 1150 #[test] 1151 fn test_color_formatter_sibling() { 1152 // A partial match on one rule does not eliminate other rules. 1153 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), @" hello [EOF]"); 1168 } 1169 1170 #[test] 1171 fn test_color_formatter_reverse_order() { 1172 // Rules don't match labels out of order 1173 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 // When two labels match, the innermost one wins. 1192 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 @" a1  b1  c  b2  a2 [EOF]"); 1217 } 1218 1219 #[test] 1220 fn test_color_formatter_dropped() { 1221 // Test that the style gets reset if the formatter is dropped without popping 1222 // all labels. 1223 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), @" inside [EOF]"); 1235 } 1236 1237 #[test] 1238 fn test_color_formatter_debug() { 1239 // Behaves like the color formatter, but surrounds each write with <<...>>, 1240 // adding the active labels before the actual content separated by a ::. 1241 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), @"<<outer inner:: inside >>[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 Heading: Message continues 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_fmt() is called even if the format string is empty. I don't 1295 // know if that's guaranteed, but let's record the current behavior. 1296 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 // Replayed output should be labeled. 1317 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  inner1 inner2  outer2 [EOF]"); 1325 1326 // Replayed output should be split at push/pop_label() call. 1327 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 >><< inner1 inner2 >><< outer2 >>[EOF]"); 1339 } 1340 1341 #[test] 1342 fn test_raw_format_recorder() { 1343 // Note: similar to test_format_recorder above 1344 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 // Replayed raw escape sequences are labeled. 1353 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  inner1 inner2  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  inner1 inner2  outer2 [EOF]"); 1374 } 1375}