1 use std::collections::VecDeque;
6 use crate::config::{Color, Config, Verbosity};
8 #[derive(Debug, PartialEq)]
15 #[derive(Debug, PartialEq)]
17 /// The line number in the formatted version.
19 /// The line number in the original version.
20 pub line_number_orig: u32,
21 /// The set of lines (context and old/new) in the mismatch.
22 pub lines: Vec<DiffLine>,
26 fn new(line_number: u32, line_number_orig: u32) -> Mismatch {
35 /// A single span of changed lines, with 0 or more removed lines
36 /// and a vector of 0 or more inserted lines.
37 #[derive(Debug, PartialEq, Eq)]
38 pub struct ModifiedChunk {
39 /// The first to be removed from the original text
40 pub line_number_orig: u32,
41 /// The number of lines which have been replaced
42 pub lines_removed: u32,
44 pub lines: Vec<String>,
47 /// Set of changed sections of a file.
48 #[derive(Debug, PartialEq, Eq)]
49 pub struct ModifiedLines {
50 /// The set of changed chunks.
51 pub chunks: Vec<ModifiedChunk>,
54 impl From<Vec<Mismatch>> for ModifiedLines {
55 fn from(mismatches: Vec<Mismatch>) -> ModifiedLines {
56 let chunks = mismatches.into_iter().map(|mismatch| {
57 let lines = mismatch.lines.iter();
58 let num_removed = lines
59 .filter(|line| match line {
60 DiffLine::Resulting(_) => true,
65 let new_lines = mismatch.lines.into_iter().filter_map(|line| match line {
66 DiffLine::Context(_) | DiffLine::Resulting(_) => None,
67 DiffLine::Expected(str) => Some(str),
71 line_number_orig: mismatch.line_number_orig,
72 lines_removed: num_removed as u32,
73 lines: new_lines.collect(),
78 chunks: chunks.collect(),
83 // Converts a `Mismatch` into a serialized form, which just includes
84 // enough information to modify the original file.
85 // Each section starts with a line with three integers, space separated:
86 // lineno num_removed num_added
87 // followed by (`num_added`) lines of added text. The line numbers are
88 // relative to the original file.
89 impl fmt::Display for ModifiedLines {
90 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91 for chunk in &self.chunks {
95 chunk.line_number_orig,
97 chunk.lines.iter().count()
100 for line in &chunk.lines {
101 writeln!(f, "{}", line)?;
109 // Allows to convert `Display`ed `ModifiedLines` back to the structural data.
110 impl std::str::FromStr for ModifiedLines {
113 fn from_str(s: &str) -> Result<ModifiedLines, ()> {
114 let mut chunks = vec![];
116 let mut lines = s.lines();
117 while let Some(header) = lines.next() {
118 let mut header = header.split_whitespace();
119 let (orig, rem, new_lines) = match (header.next(), header.next(), header.next()) {
120 (Some(orig), Some(removed), Some(added)) => (orig, removed, added),
123 let (orig, rem, new_lines): (u32, u32, usize) =
124 match (orig.parse(), rem.parse(), new_lines.parse()) {
125 (Ok(a), Ok(b), Ok(c)) => (a, b, c),
128 let lines = lines.by_ref().take(new_lines);
129 let lines: Vec<_> = lines.map(ToOwned::to_owned).collect();
130 if lines.len() != new_lines {
134 chunks.push(ModifiedChunk {
135 line_number_orig: orig,
141 Ok(ModifiedLines { chunks })
145 // This struct handles writing output to stdout and abstracts away the logic
146 // of printing in color, if it's possible in the executing environment.
147 pub(crate) struct OutputWriter {
148 terminal: Option<Box<dyn term::Terminal<Output = io::Stdout>>>,
152 // Create a new OutputWriter instance based on the caller's preference
153 // for colorized output and the capabilities of the terminal.
154 pub(crate) fn new(color: Color) -> Self {
155 if let Some(t) = term::stdout() {
156 if color.use_colored_tty() && t.supports_color() {
157 return OutputWriter { terminal: Some(t) };
160 OutputWriter { terminal: None }
163 // Write output in the optionally specified color. The output is written
164 // in the specified color if this OutputWriter instance contains a
165 // Terminal in its `terminal` field.
166 pub(crate) fn writeln(&mut self, msg: &str, color: Option<term::color::Color>) {
167 match &mut self.terminal {
169 if let Some(color) = color {
170 t.fg(color).unwrap();
172 writeln!(t, "{}", msg).unwrap();
177 None => println!("{}", msg),
182 // Produces a diff between the expected output and actual output of rustfmt.
183 pub(crate) fn make_diff(expected: &str, actual: &str, context_size: usize) -> Vec<Mismatch> {
184 let mut line_number = 1;
185 let mut line_number_orig = 1;
186 let mut context_queue: VecDeque<&str> = VecDeque::with_capacity(context_size);
187 let mut lines_since_mismatch = context_size + 1;
188 let mut results = Vec::new();
189 let mut mismatch = Mismatch::new(0, 0);
191 for result in diff::lines(expected, actual) {
193 diff::Result::Left(str) => {
194 if lines_since_mismatch >= context_size && lines_since_mismatch > 0 {
195 results.push(mismatch);
196 mismatch = Mismatch::new(
197 line_number - context_queue.len() as u32,
198 line_number_orig - context_queue.len() as u32,
202 while let Some(line) = context_queue.pop_front() {
203 mismatch.lines.push(DiffLine::Context(line.to_owned()));
206 mismatch.lines.push(DiffLine::Resulting(str.to_owned()));
207 line_number_orig += 1;
208 lines_since_mismatch = 0;
210 diff::Result::Right(str) => {
211 if lines_since_mismatch >= context_size && lines_since_mismatch > 0 {
212 results.push(mismatch);
213 mismatch = Mismatch::new(
214 line_number - context_queue.len() as u32,
215 line_number_orig - context_queue.len() as u32,
219 while let Some(line) = context_queue.pop_front() {
220 mismatch.lines.push(DiffLine::Context(line.to_owned()));
223 mismatch.lines.push(DiffLine::Expected(str.to_owned()));
225 lines_since_mismatch = 0;
227 diff::Result::Both(str, _) => {
228 if context_queue.len() >= context_size {
229 let _ = context_queue.pop_front();
232 if lines_since_mismatch < context_size {
233 mismatch.lines.push(DiffLine::Context(str.to_owned()));
234 } else if context_size > 0 {
235 context_queue.push_back(str);
239 line_number_orig += 1;
240 lines_since_mismatch += 1;
245 results.push(mismatch);
251 pub(crate) fn print_diff<F>(diff: Vec<Mismatch>, get_section_title: F, config: &Config)
253 F: Fn(u32) -> String,
255 let color = config.color();
256 let line_terminator = if config.verbose() == Verbosity::Verbose {
262 let mut writer = OutputWriter::new(color);
264 for mismatch in diff {
265 let title = get_section_title(mismatch.line_number_orig);
266 writer.writeln(&title, None);
268 for line in mismatch.lines {
270 DiffLine::Context(ref str) => {
271 writer.writeln(&format!(" {}{}", str, line_terminator), None)
273 DiffLine::Expected(ref str) => writer.writeln(
274 &format!("+{}{}", str, line_terminator),
275 Some(term::color::GREEN),
277 DiffLine::Resulting(ref str) => writer.writeln(
278 &format!("-{}{}", str, line_terminator),
279 Some(term::color::RED),
288 use super::DiffLine::*;
289 use super::{make_diff, Mismatch};
290 use super::{ModifiedChunk, ModifiedLines};
294 let src = "one\ntwo\nthree\nfour\nfive\n";
295 let dest = "one\ntwo\ntrois\nfour\nfive\n";
296 let diff = make_diff(src, dest, 1);
303 Context("two".to_owned()),
304 Resulting("three".to_owned()),
305 Expected("trois".to_owned()),
306 Context("four".to_owned()),
314 let src = "one\ntwo\nthree\nfour\nfive\nsix\nseven\n";
315 let dest = "one\ntwo\ntrois\nfour\ncinq\nsix\nseven\n";
316 let diff = make_diff(src, dest, 1);
324 Context("two".to_owned()),
325 Resulting("three".to_owned()),
326 Expected("trois".to_owned()),
327 Context("four".to_owned()),
334 Resulting("five".to_owned()),
335 Expected("cinq".to_owned()),
336 Context("six".to_owned()),
344 fn diff_zerocontext() {
345 let src = "one\ntwo\nthree\nfour\nfive\n";
346 let dest = "one\ntwo\ntrois\nfour\nfive\n";
347 let diff = make_diff(src, dest, 0);
353 lines: vec![Resulting("three".to_owned()), Expected("trois".to_owned())],
359 fn diff_trailing_newline() {
360 let src = "one\ntwo\nthree\nfour\nfive";
361 let dest = "one\ntwo\nthree\nfour\nfive\n";
362 let diff = make_diff(src, dest, 1);
368 lines: vec![Context("five".to_owned()), Expected("".to_owned())],
374 fn modified_lines_from_str() {
375 use std::str::FromStr;
377 let src = "1 6 2\nfn some() {}\nfn main() {}\n25 3 1\n struct Test {}";
378 let lines = ModifiedLines::from_str(src).unwrap();
386 lines: vec!["fn some() {}".to_owned(), "fn main() {}".to_owned(),]
389 line_number_orig: 25,
391 lines: vec![" struct Test {}".to_owned()]
398 assert_eq!(ModifiedLines::from_str(src), Err(()));
400 let src = "1 5 3\na\nb";
401 assert_eq!(ModifiedLines::from_str(src), Err(()));