1 use std::collections::VecDeque;
8 use crate::config::{Color, Config, Verbosity};
10 #[derive(Debug, PartialEq)]
17 #[derive(Debug, PartialEq)]
19 /// The line number in the formatted version.
21 /// The line number in the original version.
22 pub line_number_orig: u32,
23 /// The set of lines (context and old/new) in the mismatch.
24 pub lines: Vec<DiffLine>,
28 fn new(line_number: u32, line_number_orig: u32) -> Mismatch {
37 /// A single span of changed lines, with 0 or more removed lines
38 /// and a vector of 0 or more inserted lines.
39 #[derive(Debug, PartialEq, Eq)]
40 pub struct ModifiedChunk {
41 /// The first to be removed from the original text
42 pub line_number_orig: u32,
43 /// The number of lines which have been replaced
44 pub lines_removed: u32,
46 pub lines: Vec<String>,
49 /// Set of changed sections of a file.
50 #[derive(Debug, PartialEq, Eq)]
51 pub struct ModifiedLines {
52 /// The set of changed chunks.
53 pub chunks: Vec<ModifiedChunk>,
56 impl From<Vec<Mismatch>> for ModifiedLines {
57 fn from(mismatches: Vec<Mismatch>) -> ModifiedLines {
58 let chunks = mismatches.into_iter().map(|mismatch| {
59 let lines = mismatch.lines.iter();
60 let num_removed = lines
61 .filter(|line| match line {
62 DiffLine::Resulting(_) => true,
67 let new_lines = mismatch.lines.into_iter().filter_map(|line| match line {
68 DiffLine::Context(_) | DiffLine::Resulting(_) => None,
69 DiffLine::Expected(str) => Some(str),
73 line_number_orig: mismatch.line_number_orig,
74 lines_removed: num_removed as u32,
75 lines: new_lines.collect(),
80 chunks: chunks.collect(),
85 // Converts a `Mismatch` into a serialized form, which just includes
86 // enough information to modify the original file.
87 // Each section starts with a line with three integers, space separated:
88 // lineno num_removed num_added
89 // followed by (`num_added`) lines of added text. The line numbers are
90 // relative to the original file.
91 impl fmt::Display for ModifiedLines {
92 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93 for chunk in &self.chunks {
97 chunk.line_number_orig,
99 chunk.lines.iter().count()
102 for line in &chunk.lines {
103 writeln!(f, "{}", line)?;
111 // Allows to convert `Display`ed `ModifiedLines` back to the structural data.
112 impl std::str::FromStr for ModifiedLines {
115 fn from_str(s: &str) -> Result<ModifiedLines, ()> {
116 let mut chunks = vec![];
118 let mut lines = s.lines();
119 while let Some(header) = lines.next() {
120 let mut header = header.split_whitespace();
121 let (orig, rem, new_lines) = match (header.next(), header.next(), header.next()) {
122 (Some(orig), Some(removed), Some(added)) => (orig, removed, added),
125 let (orig, rem, new_lines): (u32, u32, usize) =
126 match (orig.parse(), rem.parse(), new_lines.parse()) {
127 (Ok(a), Ok(b), Ok(c)) => (a, b, c),
130 let lines = lines.by_ref().take(new_lines);
131 let lines: Vec<_> = lines.map(ToOwned::to_owned).collect();
132 if lines.len() != new_lines {
136 chunks.push(ModifiedChunk {
137 line_number_orig: orig,
143 Ok(ModifiedLines { chunks })
147 // This struct handles writing output to stdout and abstracts away the logic
148 // of printing in color, if it's possible in the executing environment.
149 pub struct OutputWriter {
150 terminal: Option<Box<dyn term::Terminal<Output = io::Stdout>>>,
154 // Create a new OutputWriter instance based on the caller's preference
155 // for colorized output and the capabilities of the terminal.
156 pub fn new(color: Color) -> Self {
157 if let Some(t) = term::stdout() {
158 if color.use_colored_tty() && t.supports_color() {
159 return OutputWriter { terminal: Some(t) };
162 OutputWriter { terminal: None }
165 // Write output in the optionally specified color. The output is written
166 // in the specified color if this OutputWriter instance contains a
167 // Terminal in its `terminal` field.
168 pub fn writeln(&mut self, msg: &str, color: Option<term::color::Color>) {
169 match &mut self.terminal {
171 if let Some(color) = color {
172 t.fg(color).unwrap();
174 writeln!(t, "{}", msg).unwrap();
179 None => println!("{}", msg),
184 // Produces a diff between the expected output and actual output of rustfmt.
185 pub fn make_diff(expected: &str, actual: &str, context_size: usize) -> Vec<Mismatch> {
186 let mut line_number = 1;
187 let mut line_number_orig = 1;
188 let mut context_queue: VecDeque<&str> = VecDeque::with_capacity(context_size);
189 let mut lines_since_mismatch = context_size + 1;
190 let mut results = Vec::new();
191 let mut mismatch = Mismatch::new(0, 0);
193 for result in diff::lines(expected, actual) {
195 diff::Result::Left(str) => {
196 if lines_since_mismatch >= context_size && lines_since_mismatch > 0 {
197 results.push(mismatch);
198 mismatch = Mismatch::new(
199 line_number - context_queue.len() as u32,
200 line_number_orig - context_queue.len() as u32,
204 while let Some(line) = context_queue.pop_front() {
205 mismatch.lines.push(DiffLine::Context(line.to_owned()));
208 mismatch.lines.push(DiffLine::Resulting(str.to_owned()));
209 line_number_orig += 1;
210 lines_since_mismatch = 0;
212 diff::Result::Right(str) => {
213 if lines_since_mismatch >= context_size && lines_since_mismatch > 0 {
214 results.push(mismatch);
215 mismatch = Mismatch::new(
216 line_number - context_queue.len() as u32,
217 line_number_orig - context_queue.len() as u32,
221 while let Some(line) = context_queue.pop_front() {
222 mismatch.lines.push(DiffLine::Context(line.to_owned()));
225 mismatch.lines.push(DiffLine::Expected(str.to_owned()));
227 lines_since_mismatch = 0;
229 diff::Result::Both(str, _) => {
230 if context_queue.len() >= context_size {
231 let _ = context_queue.pop_front();
234 if lines_since_mismatch < context_size {
235 mismatch.lines.push(DiffLine::Context(str.to_owned()));
236 } else if context_size > 0 {
237 context_queue.push_back(str);
241 line_number_orig += 1;
242 lines_since_mismatch += 1;
247 results.push(mismatch);
253 pub fn print_diff<F>(diff: Vec<Mismatch>, get_section_title: F, config: &Config)
255 F: Fn(u32) -> String,
257 let color = config.color();
258 let line_terminator = if config.verbose() == Verbosity::Verbose {
264 let mut writer = OutputWriter::new(color);
266 for mismatch in diff {
267 let title = get_section_title(mismatch.line_number_orig);
268 writer.writeln(&title, None);
270 for line in mismatch.lines {
272 DiffLine::Context(ref str) => {
273 writer.writeln(&format!(" {}{}", str, line_terminator), None)
275 DiffLine::Expected(ref str) => writer.writeln(
276 &format!("+{}{}", str, line_terminator),
277 Some(term::color::GREEN),
279 DiffLine::Resulting(ref str) => writer.writeln(
280 &format!("-{}{}", str, line_terminator),
281 Some(term::color::RED),
290 use super::DiffLine::*;
291 use super::{make_diff, Mismatch};
292 use super::{ModifiedChunk, ModifiedLines};
296 let src = "one\ntwo\nthree\nfour\nfive\n";
297 let dest = "one\ntwo\ntrois\nfour\nfive\n";
298 let diff = make_diff(src, dest, 1);
305 Context("two".to_owned()),
306 Resulting("three".to_owned()),
307 Expected("trois".to_owned()),
308 Context("four".to_owned()),
316 let src = "one\ntwo\nthree\nfour\nfive\nsix\nseven\n";
317 let dest = "one\ntwo\ntrois\nfour\ncinq\nsix\nseven\n";
318 let diff = make_diff(src, dest, 1);
326 Context("two".to_owned()),
327 Resulting("three".to_owned()),
328 Expected("trois".to_owned()),
329 Context("four".to_owned()),
336 Resulting("five".to_owned()),
337 Expected("cinq".to_owned()),
338 Context("six".to_owned()),
346 fn diff_zerocontext() {
347 let src = "one\ntwo\nthree\nfour\nfive\n";
348 let dest = "one\ntwo\ntrois\nfour\nfive\n";
349 let diff = make_diff(src, dest, 0);
355 lines: vec![Resulting("three".to_owned()), Expected("trois".to_owned())],
361 fn diff_trailing_newline() {
362 let src = "one\ntwo\nthree\nfour\nfive";
363 let dest = "one\ntwo\nthree\nfour\nfive\n";
364 let diff = make_diff(src, dest, 1);
370 lines: vec![Context("five".to_owned()), Expected("".to_owned())],
376 fn modified_lines_from_str() {
377 use std::str::FromStr;
379 let src = "1 6 2\nfn some() {}\nfn main() {}\n25 3 1\n struct Test {}";
380 let lines = ModifiedLines::from_str(src).unwrap();
388 lines: vec!["fn some() {}".to_owned(), "fn main() {}".to_owned(),]
391 line_number_orig: 25,
393 lines: vec![" struct Test {}".to_owned()]
400 assert_eq!(ModifiedLines::from_str(src), Err(()));
402 let src = "1 5 3\na\nb";
403 assert_eq!(ModifiedLines::from_str(src), Err(()));