|  | use std::collections::VecDeque; | 
|  | use std::fmt; | 
|  | use std::io; | 
|  | use std::io::Write; | 
|  |  | 
|  | use crate::config::{Color, Config, Verbosity}; | 
|  |  | 
|  | #[derive(Debug, PartialEq)] | 
|  | pub enum DiffLine { | 
|  | Context(String), | 
|  | Expected(String), | 
|  | Resulting(String), | 
|  | } | 
|  |  | 
|  | #[derive(Debug, PartialEq)] | 
|  | pub struct Mismatch { | 
|  | /// The line number in the formatted version. | 
|  | pub line_number: u32, | 
|  | /// The line number in the original version. | 
|  | pub line_number_orig: u32, | 
|  | /// The set of lines (context and old/new) in the mismatch. | 
|  | pub lines: Vec<DiffLine>, | 
|  | } | 
|  |  | 
|  | impl Mismatch { | 
|  | fn new(line_number: u32, line_number_orig: u32) -> Mismatch { | 
|  | Mismatch { | 
|  | line_number, | 
|  | line_number_orig, | 
|  | lines: Vec::new(), | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /// A single span of changed lines, with 0 or more removed lines | 
|  | /// and a vector of 0 or more inserted lines. | 
|  | #[derive(Debug, PartialEq, Eq)] | 
|  | pub struct ModifiedChunk { | 
|  | /// The first to be removed from the original text | 
|  | pub line_number_orig: u32, | 
|  | /// The number of lines which have been replaced | 
|  | pub lines_removed: u32, | 
|  | /// The new lines | 
|  | pub lines: Vec<String>, | 
|  | } | 
|  |  | 
|  | /// Set of changed sections of a file. | 
|  | #[derive(Debug, PartialEq, Eq)] | 
|  | pub struct ModifiedLines { | 
|  | /// The set of changed chunks. | 
|  | pub chunks: Vec<ModifiedChunk>, | 
|  | } | 
|  |  | 
|  | impl From<Vec<Mismatch>> for ModifiedLines { | 
|  | fn from(mismatches: Vec<Mismatch>) -> ModifiedLines { | 
|  | let chunks = mismatches.into_iter().map(|mismatch| { | 
|  | let lines = mismatch.lines.iter(); | 
|  | let num_removed = lines | 
|  | .filter(|line| match line { | 
|  | DiffLine::Resulting(_) => true, | 
|  | _ => false, | 
|  | }) | 
|  | .count(); | 
|  |  | 
|  | let new_lines = mismatch.lines.into_iter().filter_map(|line| match line { | 
|  | DiffLine::Context(_) | DiffLine::Resulting(_) => None, | 
|  | DiffLine::Expected(str) => Some(str), | 
|  | }); | 
|  |  | 
|  | ModifiedChunk { | 
|  | line_number_orig: mismatch.line_number_orig, | 
|  | lines_removed: num_removed as u32, | 
|  | lines: new_lines.collect(), | 
|  | } | 
|  | }); | 
|  |  | 
|  | ModifiedLines { | 
|  | chunks: chunks.collect(), | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Converts a `Mismatch` into a serialized form, which just includes | 
|  | // enough information to modify the original file. | 
|  | // Each section starts with a line with three integers, space separated: | 
|  | //     lineno num_removed num_added | 
|  | // followed by (`num_added`) lines of added text. The line numbers are | 
|  | // relative to the original file. | 
|  | impl fmt::Display for ModifiedLines { | 
|  | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | 
|  | for chunk in &self.chunks { | 
|  | writeln!( | 
|  | f, | 
|  | "{} {} {}", | 
|  | chunk.line_number_orig, | 
|  | chunk.lines_removed, | 
|  | chunk.lines.iter().count() | 
|  | )?; | 
|  |  | 
|  | for line in &chunk.lines { | 
|  | writeln!(f, "{}", line)?; | 
|  | } | 
|  | } | 
|  |  | 
|  | Ok(()) | 
|  | } | 
|  | } | 
|  |  | 
|  | // Allows to convert `Display`ed `ModifiedLines` back to the structural data. | 
|  | impl std::str::FromStr for ModifiedLines { | 
|  | type Err = (); | 
|  |  | 
|  | fn from_str(s: &str) -> Result<ModifiedLines, ()> { | 
|  | let mut chunks = vec![]; | 
|  |  | 
|  | let mut lines = s.lines(); | 
|  | while let Some(header) = lines.next() { | 
|  | let mut header = header.split_whitespace(); | 
|  | let (orig, rem, new_lines) = match (header.next(), header.next(), header.next()) { | 
|  | (Some(orig), Some(removed), Some(added)) => (orig, removed, added), | 
|  | _ => return Err(()), | 
|  | }; | 
|  | let (orig, rem, new_lines): (u32, u32, usize) = | 
|  | match (orig.parse(), rem.parse(), new_lines.parse()) { | 
|  | (Ok(a), Ok(b), Ok(c)) => (a, b, c), | 
|  | _ => return Err(()), | 
|  | }; | 
|  | let lines = lines.by_ref().take(new_lines); | 
|  | let lines: Vec<_> = lines.map(ToOwned::to_owned).collect(); | 
|  | if lines.len() != new_lines { | 
|  | return Err(()); | 
|  | } | 
|  |  | 
|  | chunks.push(ModifiedChunk { | 
|  | line_number_orig: orig, | 
|  | lines_removed: rem, | 
|  | lines, | 
|  | }); | 
|  | } | 
|  |  | 
|  | Ok(ModifiedLines { chunks }) | 
|  | } | 
|  | } | 
|  |  | 
|  | // This struct handles writing output to stdout and abstracts away the logic | 
|  | // of printing in color, if it's possible in the executing environment. | 
|  | pub(crate) struct OutputWriter { | 
|  | terminal: Option<Box<dyn term::Terminal<Output = io::Stdout>>>, | 
|  | } | 
|  |  | 
|  | impl OutputWriter { | 
|  | // Create a new OutputWriter instance based on the caller's preference | 
|  | // for colorized output and the capabilities of the terminal. | 
|  | pub(crate) fn new(color: Color) -> Self { | 
|  | if let Some(t) = term::stdout() { | 
|  | if color.use_colored_tty() && t.supports_color() { | 
|  | return OutputWriter { terminal: Some(t) }; | 
|  | } | 
|  | } | 
|  | OutputWriter { terminal: None } | 
|  | } | 
|  |  | 
|  | // Write output in the optionally specified color. The output is written | 
|  | // in the specified color if this OutputWriter instance contains a | 
|  | // Terminal in its `terminal` field. | 
|  | pub(crate) fn writeln(&mut self, msg: &str, color: Option<term::color::Color>) { | 
|  | match &mut self.terminal { | 
|  | Some(ref mut t) => { | 
|  | if let Some(color) = color { | 
|  | t.fg(color).unwrap(); | 
|  | } | 
|  | writeln!(t, "{}", msg).unwrap(); | 
|  | if color.is_some() { | 
|  | t.reset().unwrap(); | 
|  | } | 
|  | } | 
|  | None => println!("{}", msg), | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Produces a diff between the expected output and actual output of rustfmt. | 
|  | pub(crate) fn make_diff(expected: &str, actual: &str, context_size: usize) -> Vec<Mismatch> { | 
|  | let mut line_number = 1; | 
|  | let mut line_number_orig = 1; | 
|  | let mut context_queue: VecDeque<&str> = VecDeque::with_capacity(context_size); | 
|  | let mut lines_since_mismatch = context_size + 1; | 
|  | let mut results = Vec::new(); | 
|  | let mut mismatch = Mismatch::new(0, 0); | 
|  |  | 
|  | for result in diff::lines(expected, actual) { | 
|  | match result { | 
|  | diff::Result::Left(str) => { | 
|  | if lines_since_mismatch >= context_size && lines_since_mismatch > 0 { | 
|  | results.push(mismatch); | 
|  | mismatch = Mismatch::new( | 
|  | line_number - context_queue.len() as u32, | 
|  | line_number_orig - context_queue.len() as u32, | 
|  | ); | 
|  | } | 
|  |  | 
|  | while let Some(line) = context_queue.pop_front() { | 
|  | mismatch.lines.push(DiffLine::Context(line.to_owned())); | 
|  | } | 
|  |  | 
|  | mismatch.lines.push(DiffLine::Resulting(str.to_owned())); | 
|  | line_number_orig += 1; | 
|  | lines_since_mismatch = 0; | 
|  | } | 
|  | diff::Result::Right(str) => { | 
|  | if lines_since_mismatch >= context_size && lines_since_mismatch > 0 { | 
|  | results.push(mismatch); | 
|  | mismatch = Mismatch::new( | 
|  | line_number - context_queue.len() as u32, | 
|  | line_number_orig - context_queue.len() as u32, | 
|  | ); | 
|  | } | 
|  |  | 
|  | while let Some(line) = context_queue.pop_front() { | 
|  | mismatch.lines.push(DiffLine::Context(line.to_owned())); | 
|  | } | 
|  |  | 
|  | mismatch.lines.push(DiffLine::Expected(str.to_owned())); | 
|  | line_number += 1; | 
|  | lines_since_mismatch = 0; | 
|  | } | 
|  | diff::Result::Both(str, _) => { | 
|  | if context_queue.len() >= context_size { | 
|  | let _ = context_queue.pop_front(); | 
|  | } | 
|  |  | 
|  | if lines_since_mismatch < context_size { | 
|  | mismatch.lines.push(DiffLine::Context(str.to_owned())); | 
|  | } else if context_size > 0 { | 
|  | context_queue.push_back(str); | 
|  | } | 
|  |  | 
|  | line_number += 1; | 
|  | line_number_orig += 1; | 
|  | lines_since_mismatch += 1; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | results.push(mismatch); | 
|  | results.remove(0); | 
|  |  | 
|  | results | 
|  | } | 
|  |  | 
|  | pub(crate) fn print_diff<F>(diff: Vec<Mismatch>, get_section_title: F, config: &Config) | 
|  | where | 
|  | F: Fn(u32) -> String, | 
|  | { | 
|  | let color = config.color(); | 
|  | let line_terminator = if config.verbose() == Verbosity::Verbose { | 
|  | "⏎" | 
|  | } else { | 
|  | "" | 
|  | }; | 
|  |  | 
|  | let mut writer = OutputWriter::new(color); | 
|  |  | 
|  | for mismatch in diff { | 
|  | let title = get_section_title(mismatch.line_number_orig); | 
|  | writer.writeln(&title, None); | 
|  |  | 
|  | for line in mismatch.lines { | 
|  | match line { | 
|  | DiffLine::Context(ref str) => { | 
|  | writer.writeln(&format!(" {}{}", str, line_terminator), None) | 
|  | } | 
|  | DiffLine::Expected(ref str) => writer.writeln( | 
|  | &format!("+{}{}", str, line_terminator), | 
|  | Some(term::color::GREEN), | 
|  | ), | 
|  | DiffLine::Resulting(ref str) => writer.writeln( | 
|  | &format!("-{}{}", str, line_terminator), | 
|  | Some(term::color::RED), | 
|  | ), | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | #[cfg(test)] | 
|  | mod test { | 
|  | use super::DiffLine::*; | 
|  | use super::{make_diff, Mismatch}; | 
|  | use super::{ModifiedChunk, ModifiedLines}; | 
|  |  | 
|  | #[test] | 
|  | fn diff_simple() { | 
|  | let src = "one\ntwo\nthree\nfour\nfive\n"; | 
|  | let dest = "one\ntwo\ntrois\nfour\nfive\n"; | 
|  | let diff = make_diff(src, dest, 1); | 
|  | assert_eq!( | 
|  | diff, | 
|  | vec![Mismatch { | 
|  | line_number: 2, | 
|  | line_number_orig: 2, | 
|  | lines: vec![ | 
|  | Context("two".to_owned()), | 
|  | Resulting("three".to_owned()), | 
|  | Expected("trois".to_owned()), | 
|  | Context("four".to_owned()), | 
|  | ], | 
|  | }] | 
|  | ); | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn diff_simple2() { | 
|  | let src = "one\ntwo\nthree\nfour\nfive\nsix\nseven\n"; | 
|  | let dest = "one\ntwo\ntrois\nfour\ncinq\nsix\nseven\n"; | 
|  | let diff = make_diff(src, dest, 1); | 
|  | assert_eq!( | 
|  | diff, | 
|  | vec![ | 
|  | Mismatch { | 
|  | line_number: 2, | 
|  | line_number_orig: 2, | 
|  | lines: vec![ | 
|  | Context("two".to_owned()), | 
|  | Resulting("three".to_owned()), | 
|  | Expected("trois".to_owned()), | 
|  | Context("four".to_owned()), | 
|  | ], | 
|  | }, | 
|  | Mismatch { | 
|  | line_number: 5, | 
|  | line_number_orig: 5, | 
|  | lines: vec![ | 
|  | Resulting("five".to_owned()), | 
|  | Expected("cinq".to_owned()), | 
|  | Context("six".to_owned()), | 
|  | ], | 
|  | }, | 
|  | ] | 
|  | ); | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn diff_zerocontext() { | 
|  | let src = "one\ntwo\nthree\nfour\nfive\n"; | 
|  | let dest = "one\ntwo\ntrois\nfour\nfive\n"; | 
|  | let diff = make_diff(src, dest, 0); | 
|  | assert_eq!( | 
|  | diff, | 
|  | vec![Mismatch { | 
|  | line_number: 3, | 
|  | line_number_orig: 3, | 
|  | lines: vec![Resulting("three".to_owned()), Expected("trois".to_owned())], | 
|  | }] | 
|  | ); | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn diff_trailing_newline() { | 
|  | let src = "one\ntwo\nthree\nfour\nfive"; | 
|  | let dest = "one\ntwo\nthree\nfour\nfive\n"; | 
|  | let diff = make_diff(src, dest, 1); | 
|  | assert_eq!( | 
|  | diff, | 
|  | vec![Mismatch { | 
|  | line_number: 5, | 
|  | line_number_orig: 5, | 
|  | lines: vec![Context("five".to_owned()), Expected("".to_owned())], | 
|  | }] | 
|  | ); | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn modified_lines_from_str() { | 
|  | use std::str::FromStr; | 
|  |  | 
|  | let src = "1 6 2\nfn some() {}\nfn main() {}\n25 3 1\n  struct Test {}"; | 
|  | let lines = ModifiedLines::from_str(src).unwrap(); | 
|  | assert_eq!( | 
|  | lines, | 
|  | ModifiedLines { | 
|  | chunks: vec![ | 
|  | ModifiedChunk { | 
|  | line_number_orig: 1, | 
|  | lines_removed: 6, | 
|  | lines: vec!["fn some() {}".to_owned(), "fn main() {}".to_owned(),] | 
|  | }, | 
|  | ModifiedChunk { | 
|  | line_number_orig: 25, | 
|  | lines_removed: 3, | 
|  | lines: vec!["  struct Test {}".to_owned()] | 
|  | } | 
|  | ] | 
|  | } | 
|  | ); | 
|  |  | 
|  | let src = "1 5 3"; | 
|  | assert_eq!(ModifiedLines::from_str(src), Err(())); | 
|  |  | 
|  | let src = "1 5 3\na\nb"; | 
|  | assert_eq!(ModifiedLines::from_str(src), Err(())); | 
|  | } | 
|  | } |