1 use std::collections::VecDeque;
7 use crate::config::{Color, Config, Verbosity};
9 #[derive(Debug, PartialEq)]
16 #[derive(Debug, PartialEq)]
18 /// The line number in the formatted version.
20 /// The line number in the original version.
21 pub line_number_orig: u32,
22 /// The set of lines (context and old/new) in the mismatch.
23 pub lines: Vec<DiffLine>,
27 fn new(line_number: u32, line_number_orig: u32) -> Mismatch {
36 // This struct handles writing output to stdout and abstracts away the logic
37 // of printing in color, if it's possible in the executing environment.
38 pub struct OutputWriter {
39 terminal: Option<Box<dyn term::Terminal<Output = io::Stdout>>>,
43 // Create a new OutputWriter instance based on the caller's preference
44 // for colorized output and the capabilities of the terminal.
45 pub fn new(color: Color) -> Self {
46 if let Some(t) = term::stdout() {
47 if color.use_colored_tty() && t.supports_color() {
48 return OutputWriter { terminal: Some(t) };
51 OutputWriter { terminal: None }
54 // Write output in the optionally specified color. The output is written
55 // in the specified color if this OutputWriter instance contains a
56 // Terminal in its `terminal` field.
57 pub fn writeln(&mut self, msg: &str, color: Option<term::color::Color>) {
58 match &mut self.terminal {
60 if let Some(color) = color {
63 writeln!(t, "{}", msg).unwrap();
68 None => println!("{}", msg),
73 // Produces a diff between the expected output and actual output of rustfmt.
74 pub fn make_diff(expected: &str, actual: &str, context_size: usize) -> Vec<Mismatch> {
75 let mut line_number = 1;
76 let mut line_number_orig = 1;
77 let mut context_queue: VecDeque<&str> = VecDeque::with_capacity(context_size);
78 let mut lines_since_mismatch = context_size + 1;
79 let mut results = Vec::new();
80 let mut mismatch = Mismatch::new(0, 0);
82 for result in diff::lines(expected, actual) {
84 diff::Result::Left(str) => {
85 if lines_since_mismatch >= context_size && lines_since_mismatch > 0 {
86 results.push(mismatch);
87 mismatch = Mismatch::new(
88 line_number - context_queue.len() as u32,
89 line_number_orig - context_queue.len() as u32,
93 while let Some(line) = context_queue.pop_front() {
94 mismatch.lines.push(DiffLine::Context(line.to_owned()));
97 mismatch.lines.push(DiffLine::Resulting(str.to_owned()));
98 line_number_orig += 1;
99 lines_since_mismatch = 0;
101 diff::Result::Right(str) => {
102 if lines_since_mismatch >= context_size && lines_since_mismatch > 0 {
103 results.push(mismatch);
104 mismatch = Mismatch::new(
105 line_number - context_queue.len() as u32,
106 line_number_orig - context_queue.len() as u32,
110 while let Some(line) = context_queue.pop_front() {
111 mismatch.lines.push(DiffLine::Context(line.to_owned()));
114 mismatch.lines.push(DiffLine::Expected(str.to_owned()));
116 lines_since_mismatch = 0;
118 diff::Result::Both(str, _) => {
119 if context_queue.len() >= context_size {
120 let _ = context_queue.pop_front();
123 if lines_since_mismatch < context_size {
124 mismatch.lines.push(DiffLine::Context(str.to_owned()));
125 } else if context_size > 0 {
126 context_queue.push_back(str);
130 line_number_orig += 1;
131 lines_since_mismatch += 1;
136 results.push(mismatch);
142 pub fn print_diff<F>(diff: Vec<Mismatch>, get_section_title: F, config: &Config)
144 F: Fn(u32) -> String,
146 let color = config.color();
147 let line_terminator = if config.verbose() == Verbosity::Verbose {
153 let mut writer = OutputWriter::new(color);
155 for mismatch in diff {
156 let title = get_section_title(mismatch.line_number_orig);
157 writer.writeln(&title, None);
159 for line in mismatch.lines {
161 DiffLine::Context(ref str) => {
162 writer.writeln(&format!(" {}{}", str, line_terminator), None)
164 DiffLine::Expected(ref str) => writer.writeln(
165 &format!("+{}{}", str, line_terminator),
166 Some(term::color::GREEN),
168 DiffLine::Resulting(ref str) => writer.writeln(
169 &format!("-{}{}", str, line_terminator),
170 Some(term::color::RED),
177 /// Converts a `Mismatch` into a serialized form, which just includes
178 /// enough information to modify the original file.
179 /// Each section starts with a line with three integers, space separated:
180 /// lineno num_removed num_added
181 /// followed by (`num_added`) lines of added text. The line numbers are
182 /// relative to the original file.
183 pub fn output_modified<W>(mut out: W, diff: Vec<Mismatch>)
187 for mismatch in diff {
188 let (num_removed, num_added) =
192 .fold((0, 0), |(rem, add), line| match *line {
193 DiffLine::Context(_) => panic!("No Context expected"),
194 DiffLine::Expected(_) => (rem, add + 1),
195 DiffLine::Resulting(_) => (rem + 1, add),
197 // Write a header with enough information to separate the modified lines.
201 mismatch.line_number_orig, num_removed, num_added
205 for line in mismatch.lines {
207 DiffLine::Context(_) | DiffLine::Resulting(_) => (),
208 DiffLine::Expected(ref str) => {
209 writeln!(out, "{}", str).unwrap();
218 use super::DiffLine::*;
219 use super::{make_diff, Mismatch};
223 let src = "one\ntwo\nthree\nfour\nfive\n";
224 let dest = "one\ntwo\ntrois\nfour\nfive\n";
225 let diff = make_diff(src, dest, 1);
232 Context("two".to_owned()),
233 Resulting("three".to_owned()),
234 Expected("trois".to_owned()),
235 Context("four".to_owned()),
243 let src = "one\ntwo\nthree\nfour\nfive\nsix\nseven\n";
244 let dest = "one\ntwo\ntrois\nfour\ncinq\nsix\nseven\n";
245 let diff = make_diff(src, dest, 1);
253 Context("two".to_owned()),
254 Resulting("three".to_owned()),
255 Expected("trois".to_owned()),
256 Context("four".to_owned()),
263 Resulting("five".to_owned()),
264 Expected("cinq".to_owned()),
265 Context("six".to_owned()),
273 fn diff_zerocontext() {
274 let src = "one\ntwo\nthree\nfour\nfive\n";
275 let dest = "one\ntwo\ntrois\nfour\nfive\n";
276 let diff = make_diff(src, dest, 0);
282 lines: vec![Resulting("three".to_owned()), Expected("trois".to_owned())],
288 fn diff_trailing_newline() {
289 let src = "one\ntwo\nthree\nfour\nfive";
290 let dest = "one\ntwo\nthree\nfour\nfive\n";
291 let diff = make_diff(src, dest, 1);
297 lines: vec![Context("five".to_owned()), Expected("".to_owned())],