just playing with tangled
at diffedit3 1170 lines 41 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::io::{Error, Write}; 18use std::ops::Range; 19use std::sync::Arc; 20use std::{fmt, io, mem}; 21 22use crossterm::queue; 23use crossterm::style::{Attribute, Color, SetAttribute, SetBackgroundColor, SetForegroundColor}; 24use itertools::Itertools; 25 26// Lets the caller label strings and translates the labels to colors 27pub trait Formatter: Write { 28 /// Returns the backing `Write`. This is useful for writing data that is 29 /// already formatted, such as in the graphical log. 30 fn raw(&mut self) -> &mut dyn Write; 31 32 fn push_label(&mut self, label: &str) -> io::Result<()>; 33 34 fn pop_label(&mut self) -> io::Result<()>; 35} 36 37impl dyn Formatter + '_ { 38 pub fn labeled<S: AsRef<str>>(&mut self, label: S) -> LabeledWriter<&mut Self, S> { 39 LabeledWriter { 40 formatter: self, 41 label, 42 } 43 } 44 45 pub fn with_label( 46 &mut self, 47 label: &str, 48 write_inner: impl FnOnce(&mut dyn Formatter) -> io::Result<()>, 49 ) -> io::Result<()> { 50 self.push_label(label)?; 51 // Call `pop_label()` whether or not `write_inner()` fails, but don't let 52 // its error replace the one from `write_inner()`. 53 write_inner(self).and(self.pop_label()) 54 } 55} 56 57/// `Formatter` wrapper to write a labeled message with `write!()` or 58/// `writeln!()`. 59pub struct LabeledWriter<T, S> { 60 formatter: T, 61 label: S, 62} 63 64impl<T, S> LabeledWriter<T, S> { 65 pub fn new(formatter: T, label: S) -> Self { 66 LabeledWriter { formatter, label } 67 } 68} 69 70impl<'a, T, S> LabeledWriter<T, S> 71where 72 T: BorrowMut<dyn Formatter + 'a>, 73 S: AsRef<str>, 74{ 75 pub fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> io::Result<()> { 76 self.with_labeled(|formatter| formatter.write_fmt(args)) 77 } 78 79 fn with_labeled( 80 &mut self, 81 write_inner: impl FnOnce(&mut dyn Formatter) -> io::Result<()>, 82 ) -> io::Result<()> { 83 self.formatter 84 .borrow_mut() 85 .with_label(self.label.as_ref(), write_inner) 86 } 87} 88 89/// Like `LabeledWriter`, but also prints the `heading` once. 90/// 91/// The `heading` will be printed within the first `write!()` or `writeln!()` 92/// invocation, which is handy because `io::Error` can be handled there. 93pub struct HeadingLabeledWriter<T, S, H> { 94 writer: LabeledWriter<T, S>, 95 heading: Option<H>, 96} 97 98impl<T, S, H> HeadingLabeledWriter<T, S, H> { 99 pub fn new(formatter: T, label: S, heading: H) -> Self { 100 HeadingLabeledWriter { 101 writer: LabeledWriter::new(formatter, label), 102 heading: Some(heading), 103 } 104 } 105} 106 107impl<'a, T, S, H> HeadingLabeledWriter<T, S, H> 108where 109 T: BorrowMut<dyn Formatter + 'a>, 110 S: AsRef<str>, 111 H: fmt::Display, 112{ 113 pub fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> io::Result<()> { 114 self.writer.with_labeled(|formatter| { 115 if let Some(heading) = self.heading.take() { 116 write!(formatter.labeled("heading"), "{heading}")?; 117 } 118 formatter.write_fmt(args) 119 }) 120 } 121} 122 123type Rules = Vec<(Vec<String>, Style)>; 124 125/// Creates `Formatter` instances with preconfigured parameters. 126#[derive(Clone, Debug)] 127pub struct FormatterFactory { 128 kind: FormatterFactoryKind, 129} 130 131#[derive(Clone, Debug)] 132enum FormatterFactoryKind { 133 PlainText, 134 Sanitized, 135 Color { rules: Arc<Rules> }, 136} 137 138impl FormatterFactory { 139 pub fn prepare( 140 config: &config::Config, 141 color: bool, 142 sanitized: bool, 143 ) -> Result<Self, config::ConfigError> { 144 let kind = if color { 145 let rules = Arc::new(rules_from_config(config)?); 146 FormatterFactoryKind::Color { rules } 147 } else if sanitized { 148 FormatterFactoryKind::Sanitized 149 } else { 150 FormatterFactoryKind::PlainText 151 }; 152 Ok(FormatterFactory { kind }) 153 } 154 155 pub fn new_formatter<'output, W: Write + 'output>( 156 &self, 157 output: W, 158 ) -> Box<dyn Formatter + 'output> { 159 match &self.kind { 160 FormatterFactoryKind::PlainText => Box::new(PlainTextFormatter::new(output)), 161 FormatterFactoryKind::Sanitized => Box::new(SanitizingFormatter::new(output)), 162 FormatterFactoryKind::Color { rules } => { 163 Box::new(ColorFormatter::new(output, rules.clone())) 164 } 165 } 166 } 167} 168 169pub struct PlainTextFormatter<W> { 170 output: W, 171} 172 173impl<W> PlainTextFormatter<W> { 174 pub fn new(output: W) -> PlainTextFormatter<W> { 175 Self { output } 176 } 177} 178 179impl<W: Write> Write for PlainTextFormatter<W> { 180 fn write(&mut self, data: &[u8]) -> Result<usize, Error> { 181 self.output.write(data) 182 } 183 184 fn flush(&mut self) -> Result<(), Error> { 185 self.output.flush() 186 } 187} 188 189impl<W: Write> Formatter for PlainTextFormatter<W> { 190 fn raw(&mut self) -> &mut dyn Write { 191 &mut self.output 192 } 193 194 fn push_label(&mut self, _label: &str) -> io::Result<()> { 195 Ok(()) 196 } 197 198 fn pop_label(&mut self) -> io::Result<()> { 199 Ok(()) 200 } 201} 202 203pub struct SanitizingFormatter<W> { 204 output: W, 205} 206 207impl<W> SanitizingFormatter<W> { 208 pub fn new(output: W) -> SanitizingFormatter<W> { 209 Self { output } 210 } 211} 212 213impl<W: Write> Write for SanitizingFormatter<W> { 214 fn write(&mut self, data: &[u8]) -> Result<usize, Error> { 215 write_sanitized(&mut self.output, data)?; 216 Ok(data.len()) 217 } 218 219 fn flush(&mut self) -> Result<(), Error> { 220 self.output.flush() 221 } 222} 223 224impl<W: Write> Formatter for SanitizingFormatter<W> { 225 fn raw(&mut self) -> &mut dyn Write { 226 &mut self.output 227 } 228 229 fn push_label(&mut self, _label: &str) -> io::Result<()> { 230 Ok(()) 231 } 232 233 fn pop_label(&mut self) -> io::Result<()> { 234 Ok(()) 235 } 236} 237 238#[derive(Clone, Debug, Default, PartialEq, Eq)] 239pub struct Style { 240 pub fg_color: Option<Color>, 241 pub bg_color: Option<Color>, 242 pub bold: Option<bool>, 243 pub underlined: Option<bool>, 244} 245 246impl Style { 247 fn merge(&mut self, other: &Style) { 248 self.fg_color = other.fg_color.or(self.fg_color); 249 self.bg_color = other.bg_color.or(self.bg_color); 250 self.bold = other.bold.or(self.bold); 251 self.underlined = other.underlined.or(self.underlined); 252 } 253} 254 255#[derive(Clone, Debug)] 256pub struct ColorFormatter<W: Write> { 257 output: W, 258 rules: Arc<Rules>, 259 /// The stack of currently applied labels. These determine the desired 260 /// style. 261 labels: Vec<String>, 262 cached_styles: HashMap<Vec<String>, Style>, 263 /// The style we last wrote to the output. 264 current_style: Style, 265} 266 267impl<W: Write> ColorFormatter<W> { 268 pub fn new(output: W, rules: Arc<Rules>) -> ColorFormatter<W> { 269 ColorFormatter { 270 output, 271 rules, 272 labels: vec![], 273 cached_styles: HashMap::new(), 274 current_style: Style::default(), 275 } 276 } 277 278 pub fn for_config(output: W, config: &config::Config) -> Result<Self, config::ConfigError> { 279 let rules = rules_from_config(config)?; 280 Ok(Self::new(output, Arc::new(rules))) 281 } 282 283 fn requested_style(&mut self) -> Style { 284 if let Some(cached) = self.cached_styles.get(&self.labels) { 285 cached.clone() 286 } else { 287 // We use the reverse list of matched indices as a measure of how well the rule 288 // matches the actual labels. For example, for rule "a d" and the actual labels 289 // "a b c d", we'll get [3,0]. We compare them by Rust's default Vec comparison. 290 // That means "a d" will trump both rule "d" (priority [3]) and rule 291 // "a b c" (priority [2,1,0]). 292 let mut matched_styles = vec![]; 293 for (labels, style) in self.rules.as_ref() { 294 let mut labels_iter = self.labels.iter().enumerate(); 295 // The indexes in the current label stack that match the required label. 296 let mut matched_indices = vec![]; 297 for required_label in labels { 298 for (label_index, label) in &mut labels_iter { 299 if label == required_label { 300 matched_indices.push(label_index); 301 break; 302 } 303 } 304 } 305 if matched_indices.len() == labels.len() { 306 matched_indices.reverse(); 307 matched_styles.push((style, matched_indices)); 308 } 309 } 310 matched_styles.sort_by_key(|(_, indices)| indices.clone()); 311 312 let mut style = Style::default(); 313 for (matched_style, _) in matched_styles { 314 style.merge(matched_style); 315 } 316 self.cached_styles 317 .insert(self.labels.clone(), style.clone()); 318 style 319 } 320 } 321 322 fn write_new_style(&mut self) -> io::Result<()> { 323 let new_style = self.requested_style(); 324 if new_style != self.current_style { 325 if new_style.bold != self.current_style.bold { 326 if new_style.bold.unwrap_or_default() { 327 queue!(self.output, SetAttribute(Attribute::Bold))?; 328 } else { 329 // NoBold results in double underlining on some terminals, so we use reset 330 // instead. However, that resets other attributes as well, so we reset 331 // our record of the current style so we re-apply the other attributes 332 // below. 333 queue!(self.output, SetAttribute(Attribute::Reset))?; 334 self.current_style = Style::default(); 335 } 336 } 337 if new_style.underlined != self.current_style.underlined { 338 if new_style.underlined.unwrap_or_default() { 339 queue!(self.output, SetAttribute(Attribute::Underlined))?; 340 } else { 341 queue!(self.output, SetAttribute(Attribute::NoUnderline))?; 342 } 343 } 344 if new_style.fg_color != self.current_style.fg_color { 345 queue!( 346 self.output, 347 SetForegroundColor(new_style.fg_color.unwrap_or(Color::Reset)) 348 )?; 349 } 350 if new_style.bg_color != self.current_style.bg_color { 351 queue!( 352 self.output, 353 SetBackgroundColor(new_style.bg_color.unwrap_or(Color::Reset)) 354 )?; 355 } 356 self.current_style = new_style; 357 } 358 Ok(()) 359 } 360} 361 362fn rules_from_config(config: &config::Config) -> Result<Rules, config::ConfigError> { 363 let mut result = vec![]; 364 let table = config.get_table("colors")?; 365 for (key, value) in table { 366 let labels = key 367 .split_whitespace() 368 .map(ToString::to_string) 369 .collect_vec(); 370 match value.kind { 371 config::ValueKind::String(color_name) => { 372 let style = Style { 373 fg_color: Some(color_for_name_or_hex(&color_name)?), 374 bg_color: None, 375 bold: None, 376 underlined: None, 377 }; 378 result.push((labels, style)); 379 } 380 config::ValueKind::Table(style_table) => { 381 let mut style = Style::default(); 382 if let Some(value) = style_table.get("fg") { 383 if let config::ValueKind::String(color_name) = &value.kind { 384 style.fg_color = Some(color_for_name_or_hex(color_name)?); 385 } 386 } 387 if let Some(value) = style_table.get("bg") { 388 if let config::ValueKind::String(color_name) = &value.kind { 389 style.bg_color = Some(color_for_name_or_hex(color_name)?); 390 } 391 } 392 if let Some(value) = style_table.get("bold") { 393 if let config::ValueKind::Boolean(value) = &value.kind { 394 style.bold = Some(*value); 395 } 396 } 397 if let Some(value) = style_table.get("underline") { 398 if let config::ValueKind::Boolean(value) = &value.kind { 399 style.underlined = Some(*value); 400 } 401 } 402 result.push((labels, style)); 403 } 404 _ => {} 405 } 406 } 407 Ok(result) 408} 409 410fn color_for_name_or_hex(name_or_hex: &str) -> Result<Color, config::ConfigError> { 411 match name_or_hex { 412 "default" => Ok(Color::Reset), 413 "black" => Ok(Color::Black), 414 "red" => Ok(Color::DarkRed), 415 "green" => Ok(Color::DarkGreen), 416 "yellow" => Ok(Color::DarkYellow), 417 "blue" => Ok(Color::DarkBlue), 418 "magenta" => Ok(Color::DarkMagenta), 419 "cyan" => Ok(Color::DarkCyan), 420 "white" => Ok(Color::Grey), 421 "bright black" => Ok(Color::DarkGrey), 422 "bright red" => Ok(Color::Red), 423 "bright green" => Ok(Color::Green), 424 "bright yellow" => Ok(Color::Yellow), 425 "bright blue" => Ok(Color::Blue), 426 "bright magenta" => Ok(Color::Magenta), 427 "bright cyan" => Ok(Color::Cyan), 428 "bright white" => Ok(Color::White), 429 _ => color_for_hex(name_or_hex) 430 .ok_or_else(|| config::ConfigError::Message(format!("invalid color: {}", name_or_hex))), 431 } 432} 433 434fn color_for_hex(color: &str) -> Option<Color> { 435 if color.len() == 7 436 && color.starts_with('#') 437 && color[1..].chars().all(|c| c.is_ascii_hexdigit()) 438 { 439 let r = u8::from_str_radix(&color[1..3], 16); 440 let g = u8::from_str_radix(&color[3..5], 16); 441 let b = u8::from_str_radix(&color[5..7], 16); 442 match (r, g, b) { 443 (Ok(r), Ok(g), Ok(b)) => Some(Color::Rgb { r, g, b }), 444 _ => None, 445 } 446 } else { 447 None 448 } 449} 450 451impl<W: Write> Write for ColorFormatter<W> { 452 fn write(&mut self, data: &[u8]) -> Result<usize, Error> { 453 /* 454 We clear the current style at the end of each line, and then we re-apply the style 455 after the newline. There are several reasons for this: 456 457 * We can more easily skip styling a trailing blank line, which other 458 internal code then can correctly detect as having a trailing 459 newline. 460 461 * Some tools (like `less -R`) add an extra newline if the final 462 character is not a newline (e.g. if there's a color reset after 463 it), which led to an annoying blank line after the diff summary in 464 e.g. `jj status`. 465 466 * Since each line is styled independently, you get all the necessary 467 escapes even when grepping through the output. 468 469 * Some terminals extend background color to the end of the terminal 470 (i.e. past the newline character), which is probably not what the 471 user wanted. 472 473 * Some tools (like `less -R`) get confused and lose coloring of lines 474 after a newline. 475 */ 476 for line in data.split_inclusive(|b| *b == b'\n') { 477 if line.ends_with(b"\n") { 478 self.write_new_style()?; 479 write_sanitized(&mut self.output, &line[..line.len() - 1])?; 480 let labels = mem::take(&mut self.labels); 481 self.write_new_style()?; 482 self.output.write_all(b"\n")?; 483 self.labels = labels; 484 } else { 485 self.write_new_style()?; 486 write_sanitized(&mut self.output, line)?; 487 } 488 } 489 Ok(data.len()) 490 } 491 492 fn flush(&mut self) -> Result<(), Error> { 493 self.output.flush() 494 } 495} 496 497impl<W: Write> Formatter for ColorFormatter<W> { 498 fn raw(&mut self) -> &mut dyn Write { 499 &mut self.output 500 } 501 502 fn push_label(&mut self, label: &str) -> io::Result<()> { 503 self.labels.push(label.to_owned()); 504 Ok(()) 505 } 506 507 fn pop_label(&mut self) -> io::Result<()> { 508 self.labels.pop(); 509 if self.labels.is_empty() { 510 self.write_new_style()? 511 } 512 Ok(()) 513 } 514} 515 516impl<W: Write> Drop for ColorFormatter<W> { 517 fn drop(&mut self) { 518 // If a `ColorFormatter` was dropped without popping all labels first (perhaps 519 // because of an error), let's still try to reset any currently active style. 520 self.labels.clear(); 521 self.write_new_style().ok(); 522 } 523} 524 525/// Like buffered formatter, but records `push`/`pop_label()` calls. 526/// 527/// This allows you to manipulate the recorded data without losing labels. 528/// The recorded data and labels can be written to another formatter. If 529/// the destination formatter has already been labeled, the recorded labels 530/// will be stacked on top of the existing labels, and the subsequent data 531/// may be colorized differently. 532#[derive(Clone, Debug, Default)] 533pub struct FormatRecorder { 534 data: Vec<u8>, 535 label_ops: Vec<(usize, LabelOp)>, 536} 537 538#[derive(Clone, Debug, Eq, PartialEq)] 539enum LabelOp { 540 PushLabel(String), 541 PopLabel, 542} 543 544impl FormatRecorder { 545 pub fn new() -> Self { 546 FormatRecorder::default() 547 } 548 549 pub fn data(&self) -> &[u8] { 550 &self.data 551 } 552 553 fn push_label_op(&mut self, op: LabelOp) { 554 self.label_ops.push((self.data.len(), op)); 555 } 556 557 pub fn replay(&self, formatter: &mut dyn Formatter) -> io::Result<()> { 558 self.replay_with(formatter, |formatter, range| { 559 formatter.write_all(&self.data[range]) 560 }) 561 } 562 563 pub fn replay_with( 564 &self, 565 formatter: &mut dyn Formatter, 566 mut write_data: impl FnMut(&mut dyn Formatter, Range<usize>) -> io::Result<()>, 567 ) -> io::Result<()> { 568 let mut last_pos = 0; 569 let mut flush_data = |formatter: &mut dyn Formatter, pos| -> io::Result<()> { 570 if last_pos != pos { 571 write_data(formatter, last_pos..pos)?; 572 last_pos = pos; 573 } 574 Ok(()) 575 }; 576 for (pos, op) in &self.label_ops { 577 flush_data(formatter, *pos)?; 578 match op { 579 LabelOp::PushLabel(label) => formatter.push_label(label)?, 580 LabelOp::PopLabel => formatter.pop_label()?, 581 } 582 } 583 flush_data(formatter, self.data.len()) 584 } 585} 586 587impl Write for FormatRecorder { 588 fn write(&mut self, data: &[u8]) -> io::Result<usize> { 589 self.data.extend_from_slice(data); 590 Ok(data.len()) 591 } 592 593 fn flush(&mut self) -> io::Result<()> { 594 Ok(()) 595 } 596} 597 598impl Formatter for FormatRecorder { 599 fn raw(&mut self) -> &mut dyn Write { 600 panic!("raw output isn't supported by FormatRecorder") 601 } 602 603 fn push_label(&mut self, label: &str) -> io::Result<()> { 604 self.push_label_op(LabelOp::PushLabel(label.to_owned())); 605 Ok(()) 606 } 607 608 fn pop_label(&mut self) -> io::Result<()> { 609 self.push_label_op(LabelOp::PopLabel); 610 Ok(()) 611 } 612} 613 614fn write_sanitized(output: &mut impl Write, buf: &[u8]) -> Result<(), Error> { 615 if buf.contains(&b'\x1b') { 616 let mut sanitized = Vec::with_capacity(buf.len()); 617 for b in buf { 618 if *b == b'\x1b' { 619 sanitized.extend_from_slice("".as_bytes()); 620 } else { 621 sanitized.push(*b); 622 } 623 } 624 output.write_all(&sanitized) 625 } else { 626 output.write_all(buf) 627 } 628} 629 630#[cfg(test)] 631mod tests { 632 use std::str; 633 634 use super::*; 635 636 fn config_from_string(text: &str) -> config::Config { 637 config::Config::builder() 638 .add_source(config::File::from_str(text, config::FileFormat::Toml)) 639 .build() 640 .unwrap() 641 } 642 643 #[test] 644 fn test_plaintext_formatter() { 645 // Test that PlainTextFormatter ignores labels. 646 let mut output: Vec<u8> = vec![]; 647 let mut formatter = PlainTextFormatter::new(&mut output); 648 formatter.push_label("warning").unwrap(); 649 write!(formatter, "hello").unwrap(); 650 formatter.pop_label().unwrap(); 651 insta::assert_snapshot!(String::from_utf8(output).unwrap(), @"hello"); 652 } 653 654 #[test] 655 fn test_plaintext_formatter_ansi_codes_in_text() { 656 // Test that ANSI codes in the input text are NOT escaped. 657 let mut output: Vec<u8> = vec![]; 658 let mut formatter = PlainTextFormatter::new(&mut output); 659 write!(formatter, "\x1b[1mactually bold\x1b[0m").unwrap(); 660 insta::assert_snapshot!(String::from_utf8(output).unwrap(), @"actually bold"); 661 } 662 663 #[test] 664 fn test_sanitizing_formatter_ansi_codes_in_text() { 665 // Test that ANSI codes in the input text are escaped. 666 let mut output: Vec<u8> = vec![]; 667 let mut formatter = SanitizingFormatter::new(&mut output); 668 write!(formatter, "\x1b[1mnot actually bold\x1b[0m").unwrap(); 669 insta::assert_snapshot!(String::from_utf8(output).unwrap(), @"␛[1mnot actually bold␛[0m"); 670 } 671 672 #[test] 673 fn test_color_formatter_color_codes() { 674 // Test the color code for each color. 675 let colors = [ 676 "black", 677 "red", 678 "green", 679 "yellow", 680 "blue", 681 "magenta", 682 "cyan", 683 "white", 684 "bright black", 685 "bright red", 686 "bright green", 687 "bright yellow", 688 "bright blue", 689 "bright magenta", 690 "bright cyan", 691 "bright white", 692 ]; 693 let mut config_builder = config::Config::builder(); 694 for color in colors { 695 // Use the color name as the label. 696 config_builder = config_builder 697 .set_override(format!("colors.{}", color.replace(' ', "-")), color) 698 .unwrap(); 699 } 700 let mut output: Vec<u8> = vec![]; 701 let mut formatter = 702 ColorFormatter::for_config(&mut output, &config_builder.build().unwrap()).unwrap(); 703 for color in colors { 704 formatter.push_label(&color.replace(' ', "-")).unwrap(); 705 write!(formatter, " {color} ").unwrap(); 706 formatter.pop_label().unwrap(); 707 writeln!(formatter).unwrap(); 708 } 709 drop(formatter); 710 insta::assert_snapshot!(String::from_utf8(output).unwrap(), @r###" 711  black  712  red  713  green  714  yellow  715  blue  716  magenta  717  cyan  718  white  719  bright black  720  bright red  721  bright green  722  bright yellow  723  bright blue  724  bright magenta  725  bright cyan  726  bright white  727 "###); 728 } 729 730 #[test] 731 fn test_color_formatter_hex_colors() { 732 // Test the color code for each color. 733 let labels_and_colors = [ 734 ["black", "#000000"], 735 ["white", "#ffffff"], 736 ["pastel-blue", "#AFE0D9"], 737 ]; 738 let mut config_builder = config::Config::builder(); 739 for [label, color] in labels_and_colors { 740 // Use the color name as the label. 741 config_builder = config_builder 742 .set_override(format!("colors.{}", label), color) 743 .unwrap(); 744 } 745 let mut output: Vec<u8> = vec![]; 746 let mut formatter = 747 ColorFormatter::for_config(&mut output, &config_builder.build().unwrap()).unwrap(); 748 for [label, _] in labels_and_colors { 749 formatter.push_label(&label.replace(' ', "-")).unwrap(); 750 write!(formatter, " {label} ").unwrap(); 751 formatter.pop_label().unwrap(); 752 writeln!(formatter).unwrap(); 753 } 754 drop(formatter); 755 insta::assert_snapshot!(String::from_utf8(output).unwrap(), @r###" 756  black  757  white  758  pastel-blue  759 "###); 760 } 761 762 #[test] 763 fn test_color_formatter_single_label() { 764 // Test that a single label can be colored and that the color is reset 765 // afterwards. 766 let config = config_from_string( 767 r#" 768 colors.inside = "green" 769 "#, 770 ); 771 let mut output: Vec<u8> = vec![]; 772 let mut formatter = ColorFormatter::for_config(&mut output, &config).unwrap(); 773 write!(formatter, " before ").unwrap(); 774 formatter.push_label("inside").unwrap(); 775 write!(formatter, " inside ").unwrap(); 776 formatter.pop_label().unwrap(); 777 write!(formatter, " after ").unwrap(); 778 drop(formatter); 779 insta::assert_snapshot!(String::from_utf8(output).unwrap(), @" before  inside  after "); 780 } 781 782 #[test] 783 fn test_color_formatter_attributes() { 784 // Test that each attribute of the style can be set and that they can be 785 // combined in a single rule or by using multiple rules. 786 let config = config_from_string( 787 r#" 788 colors.red_fg = { fg = "red" } 789 colors.blue_bg = { bg = "blue" } 790 colors.bold_font = { bold = true } 791 colors.underlined_text = { underline = true } 792 colors.multiple = { fg = "green", bg = "yellow", bold = true, underline = true } 793 "#, 794 ); 795 let mut output: Vec<u8> = vec![]; 796 let mut formatter = ColorFormatter::for_config(&mut output, &config).unwrap(); 797 formatter.push_label("red_fg").unwrap(); 798 write!(formatter, " fg only ").unwrap(); 799 formatter.pop_label().unwrap(); 800 writeln!(formatter).unwrap(); 801 formatter.push_label("blue_bg").unwrap(); 802 write!(formatter, " bg only ").unwrap(); 803 formatter.pop_label().unwrap(); 804 writeln!(formatter).unwrap(); 805 formatter.push_label("bold_font").unwrap(); 806 write!(formatter, " bold only ").unwrap(); 807 formatter.pop_label().unwrap(); 808 writeln!(formatter).unwrap(); 809 formatter.push_label("underlined_text").unwrap(); 810 write!(formatter, " underlined only ").unwrap(); 811 formatter.pop_label().unwrap(); 812 writeln!(formatter).unwrap(); 813 formatter.push_label("multiple").unwrap(); 814 write!(formatter, " single rule ").unwrap(); 815 formatter.pop_label().unwrap(); 816 writeln!(formatter).unwrap(); 817 formatter.push_label("red_fg").unwrap(); 818 formatter.push_label("blue_bg").unwrap(); 819 write!(formatter, " two rules ").unwrap(); 820 formatter.pop_label().unwrap(); 821 formatter.pop_label().unwrap(); 822 writeln!(formatter).unwrap(); 823 drop(formatter); 824 insta::assert_snapshot!(String::from_utf8(output).unwrap(), @r###" 825  fg only  826  bg only  827  bold only  828  underlined only  829  single rule  830  two rules  831 "###); 832 } 833 834 #[test] 835 fn test_color_formatter_bold_reset() { 836 // Test that we don't lose other attributes when we reset the bold attribute. 837 let config = config_from_string( 838 r#" 839 colors.not_bold = { fg = "red", bg = "blue", underline = true } 840 colors.bold_font = { bold = true } 841 "#, 842 ); 843 let mut output: Vec<u8> = vec![]; 844 let mut formatter = ColorFormatter::for_config(&mut output, &config).unwrap(); 845 formatter.push_label("not_bold").unwrap(); 846 write!(formatter, " not bold ").unwrap(); 847 formatter.push_label("bold_font").unwrap(); 848 write!(formatter, " bold ").unwrap(); 849 formatter.pop_label().unwrap(); 850 write!(formatter, " not bold again ").unwrap(); 851 formatter.pop_label().unwrap(); 852 drop(formatter); 853 insta::assert_snapshot!(String::from_utf8(output).unwrap(), @" not bold  bold  not bold again "); 854 } 855 856 #[test] 857 fn test_color_formatter_no_space() { 858 // Test that two different colors can touch. 859 let config = config_from_string( 860 r#" 861 colors.red = "red" 862 colors.green = "green" 863 "#, 864 ); 865 let mut output: Vec<u8> = vec![]; 866 let mut formatter = ColorFormatter::for_config(&mut output, &config).unwrap(); 867 write!(formatter, "before").unwrap(); 868 formatter.push_label("red").unwrap(); 869 write!(formatter, "first").unwrap(); 870 formatter.pop_label().unwrap(); 871 formatter.push_label("green").unwrap(); 872 write!(formatter, "second").unwrap(); 873 formatter.pop_label().unwrap(); 874 write!(formatter, "after").unwrap(); 875 drop(formatter); 876 insta::assert_snapshot!(String::from_utf8(output).unwrap(), @"beforefirstsecondafter"); 877 } 878 879 #[test] 880 fn test_color_formatter_ansi_codes_in_text() { 881 // Test that ANSI codes in the input text are escaped. 882 let config = config_from_string( 883 r#" 884 colors.red = "red" 885 "#, 886 ); 887 let mut output: Vec<u8> = vec![]; 888 let mut formatter = ColorFormatter::for_config(&mut output, &config).unwrap(); 889 formatter.push_label("red").unwrap(); 890 write!(formatter, "\x1b[1mnot actually bold\x1b[0m").unwrap(); 891 formatter.pop_label().unwrap(); 892 drop(formatter); 893 insta::assert_snapshot!(String::from_utf8(output).unwrap(), @"␛[1mnot actually bold␛[0m"); 894 } 895 896 #[test] 897 fn test_color_formatter_nested() { 898 // A color can be associated with a combination of labels. A more specific match 899 // overrides a less specific match. After the inner label is removed, the outer 900 // color is used again (we don't reset). 901 let config = config_from_string( 902 r#" 903 colors.outer = "blue" 904 colors.inner = "red" 905 colors."outer inner" = "green" 906 "#, 907 ); 908 let mut output: Vec<u8> = vec![]; 909 let mut formatter = ColorFormatter::for_config(&mut output, &config).unwrap(); 910 write!(formatter, " before outer ").unwrap(); 911 formatter.push_label("outer").unwrap(); 912 write!(formatter, " before inner ").unwrap(); 913 formatter.push_label("inner").unwrap(); 914 write!(formatter, " inside inner ").unwrap(); 915 formatter.pop_label().unwrap(); 916 write!(formatter, " after inner ").unwrap(); 917 formatter.pop_label().unwrap(); 918 write!(formatter, " after outer ").unwrap(); 919 drop(formatter); 920 insta::assert_snapshot!(String::from_utf8(output).unwrap(), 921 @" before outer  before inner  inside inner  after inner  after outer "); 922 } 923 924 #[test] 925 fn test_color_formatter_partial_match() { 926 // A partial match doesn't count 927 let config = config_from_string( 928 r#" 929 colors."outer inner" = "green" 930 "#, 931 ); 932 let mut output: Vec<u8> = vec![]; 933 let mut formatter = ColorFormatter::for_config(&mut output, &config).unwrap(); 934 formatter.push_label("outer").unwrap(); 935 write!(formatter, " not colored ").unwrap(); 936 formatter.push_label("inner").unwrap(); 937 write!(formatter, " colored ").unwrap(); 938 formatter.pop_label().unwrap(); 939 write!(formatter, " not colored ").unwrap(); 940 formatter.pop_label().unwrap(); 941 drop(formatter); 942 insta::assert_snapshot!(String::from_utf8(output).unwrap(), 943 @" not colored  colored  not colored "); 944 } 945 946 #[test] 947 fn test_color_formatter_unrecognized_color() { 948 // An unrecognized color causes an error. 949 let config = config_from_string( 950 r#" 951 colors."outer" = "red" 952 colors."outer inner" = "bloo" 953 "#, 954 ); 955 let mut output: Vec<u8> = vec![]; 956 let err = ColorFormatter::for_config(&mut output, &config) 957 .unwrap_err() 958 .to_string(); 959 insta::assert_snapshot!(err, 960 @"invalid color: bloo"); 961 } 962 963 #[test] 964 fn test_color_formatter_unrecognized_hex_color() { 965 // An unrecognized hex color causes an error. 966 let config = config_from_string( 967 r##" 968 colors."outer" = "red" 969 colors."outer inner" = "#ffgggg" 970 "##, 971 ); 972 let mut output: Vec<u8> = vec![]; 973 let err = ColorFormatter::for_config(&mut output, &config) 974 .unwrap_err() 975 .to_string(); 976 insta::assert_snapshot!(err, 977 @"invalid color: #ffgggg"); 978 } 979 980 #[test] 981 fn test_color_formatter_normal_color() { 982 // The "default" color resets the color. It is possible to reset only the 983 // background or only the foreground. 984 let config = config_from_string( 985 r#" 986 colors."outer" = {bg="yellow", fg="blue"} 987 colors."outer default_fg" = "default" 988 colors."outer default_bg" = {bg = "default"} 989 "#, 990 ); 991 let mut output: Vec<u8> = vec![]; 992 let mut formatter = ColorFormatter::for_config(&mut output, &config).unwrap(); 993 formatter.push_label("outer").unwrap(); 994 write!(formatter, "Blue on yellow, ").unwrap(); 995 formatter.push_label("default_fg").unwrap(); 996 write!(formatter, " default fg, ").unwrap(); 997 formatter.pop_label().unwrap(); 998 write!(formatter, " and back.\nBlue on yellow, ").unwrap(); 999 formatter.push_label("default_bg").unwrap(); 1000 write!(formatter, " default bg, ").unwrap(); 1001 formatter.pop_label().unwrap(); 1002 write!(formatter, " and back.").unwrap(); 1003 drop(formatter); 1004 insta::assert_snapshot!(String::from_utf8(output).unwrap(), 1005 @r###" 1006 Blue on yellow,  default fg,  and back. 1007 Blue on yellow,  default bg,  and back. 1008 "###); 1009 } 1010 1011 #[test] 1012 fn test_color_formatter_sibling() { 1013 // A partial match on one rule does not eliminate other rules. 1014 let config = config_from_string( 1015 r#" 1016 colors."outer1 inner1" = "red" 1017 colors.inner2 = "green" 1018 "#, 1019 ); 1020 let mut output: Vec<u8> = vec![]; 1021 let mut formatter = ColorFormatter::for_config(&mut output, &config).unwrap(); 1022 formatter.push_label("outer1").unwrap(); 1023 formatter.push_label("inner2").unwrap(); 1024 write!(formatter, " hello ").unwrap(); 1025 formatter.pop_label().unwrap(); 1026 formatter.pop_label().unwrap(); 1027 drop(formatter); 1028 insta::assert_snapshot!(String::from_utf8(output).unwrap(), 1029 @" hello "); 1030 } 1031 1032 #[test] 1033 fn test_color_formatter_reverse_order() { 1034 // Rules don't match labels out of order 1035 let config = config_from_string( 1036 r#" 1037 colors."inner outer" = "green" 1038 "#, 1039 ); 1040 let mut output: Vec<u8> = vec![]; 1041 let mut formatter = ColorFormatter::for_config(&mut output, &config).unwrap(); 1042 formatter.push_label("outer").unwrap(); 1043 formatter.push_label("inner").unwrap(); 1044 write!(formatter, " hello ").unwrap(); 1045 formatter.pop_label().unwrap(); 1046 formatter.pop_label().unwrap(); 1047 drop(formatter); 1048 insta::assert_snapshot!(String::from_utf8(output).unwrap(), @" hello "); 1049 } 1050 1051 #[test] 1052 fn test_color_formatter_innermost_wins() { 1053 // When two labels match, the innermost one wins. 1054 let config = config_from_string( 1055 r#" 1056 colors."a" = "red" 1057 colors."b" = "green" 1058 colors."a c" = "blue" 1059 colors."b c" = "yellow" 1060 "#, 1061 ); 1062 let mut output: Vec<u8> = vec![]; 1063 let mut formatter = ColorFormatter::for_config(&mut output, &config).unwrap(); 1064 formatter.push_label("a").unwrap(); 1065 write!(formatter, " a1 ").unwrap(); 1066 formatter.push_label("b").unwrap(); 1067 write!(formatter, " b1 ").unwrap(); 1068 formatter.push_label("c").unwrap(); 1069 write!(formatter, " c ").unwrap(); 1070 formatter.pop_label().unwrap(); 1071 write!(formatter, " b2 ").unwrap(); 1072 formatter.pop_label().unwrap(); 1073 write!(formatter, " a2 ").unwrap(); 1074 formatter.pop_label().unwrap(); 1075 drop(formatter); 1076 insta::assert_snapshot!(String::from_utf8(output).unwrap(), 1077 @" a1  b1  c  b2  a2 "); 1078 } 1079 1080 #[test] 1081 fn test_color_formatter_dropped() { 1082 // Test that the style gets reset if the formatter is dropped without popping 1083 // all labels. 1084 let config = config_from_string( 1085 r#" 1086 colors.outer = "green" 1087 "#, 1088 ); 1089 let mut output: Vec<u8> = vec![]; 1090 let mut formatter = ColorFormatter::for_config(&mut output, &config).unwrap(); 1091 formatter.push_label("outer").unwrap(); 1092 formatter.push_label("inner").unwrap(); 1093 write!(formatter, " inside ").unwrap(); 1094 drop(formatter); 1095 insta::assert_snapshot!(String::from_utf8(output).unwrap(), @" inside "); 1096 } 1097 1098 #[test] 1099 fn test_heading_labeled_writer() { 1100 let config = config_from_string( 1101 r#" 1102 colors.inner = "green" 1103 colors."inner heading" = "red" 1104 "#, 1105 ); 1106 let mut output: Vec<u8> = vec![]; 1107 let mut formatter: Box<dyn Formatter> = 1108 Box::new(ColorFormatter::for_config(&mut output, &config).unwrap()); 1109 HeadingLabeledWriter::new(formatter.as_mut(), "inner", "Should be noop: "); 1110 let mut writer = HeadingLabeledWriter::new(formatter.as_mut(), "inner", "Heading: "); 1111 write!(writer, "Message").unwrap(); 1112 writeln!(writer, " continues").unwrap(); 1113 drop(formatter); 1114 insta::assert_snapshot!(String::from_utf8(output).unwrap(), @r###" 1115 Heading: Message continues 1116 "###); 1117 } 1118 1119 #[test] 1120 fn test_heading_labeled_writer_empty_string() { 1121 let mut output: Vec<u8> = vec![]; 1122 let mut formatter: Box<dyn Formatter> = Box::new(PlainTextFormatter::new(&mut output)); 1123 let mut writer = HeadingLabeledWriter::new(formatter.as_mut(), "inner", "Heading: "); 1124 // write_fmt() is called even if the format string is empty. I don't 1125 // know if that's guaranteed, but let's record the current behavior. 1126 write!(writer, "").unwrap(); 1127 write!(writer, "").unwrap(); 1128 drop(formatter); 1129 insta::assert_snapshot!(String::from_utf8(output).unwrap(), @"Heading: "); 1130 } 1131 1132 #[test] 1133 fn test_format_recorder() { 1134 let mut recorder = FormatRecorder::new(); 1135 write!(recorder, " outer1 ").unwrap(); 1136 recorder.push_label("inner").unwrap(); 1137 write!(recorder, " inner1 ").unwrap(); 1138 write!(recorder, " inner2 ").unwrap(); 1139 recorder.pop_label().unwrap(); 1140 write!(recorder, " outer2 ").unwrap(); 1141 1142 insta::assert_snapshot!( 1143 str::from_utf8(recorder.data()).unwrap(), 1144 @" outer1 inner1 inner2 outer2 "); 1145 1146 // Replayed output should be labeled. 1147 let config = config_from_string(r#" colors.inner = "red" "#); 1148 let mut output: Vec<u8> = vec![]; 1149 let mut formatter = ColorFormatter::for_config(&mut output, &config).unwrap(); 1150 recorder.replay(&mut formatter).unwrap(); 1151 drop(formatter); 1152 insta::assert_snapshot!( 1153 String::from_utf8(output).unwrap(), 1154 @" outer1  inner1 inner2  outer2 "); 1155 1156 // Replayed output should be split at push/pop_label() call. 1157 let mut output: Vec<u8> = vec![]; 1158 let mut formatter = ColorFormatter::for_config(&mut output, &config).unwrap(); 1159 recorder 1160 .replay_with(&mut formatter, |formatter, range| { 1161 let data = &recorder.data()[range]; 1162 write!(formatter, "<<{}>>", str::from_utf8(data).unwrap()) 1163 }) 1164 .unwrap(); 1165 drop(formatter); 1166 insta::assert_snapshot!( 1167 String::from_utf8(output).unwrap(), 1168 @"<< outer1 >><< inner1 inner2 >><< outer2 >>"); 1169 } 1170}