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
| matches
!(line
, DiffLine
::Resulting(_
)))
62 let new_lines
= mismatch
.lines
.into_iter().filter_map(|line
| match line
{
63 DiffLine
::Context(_
) | DiffLine
::Resulting(_
) => None
,
64 DiffLine
::Expected(str) => Some(str),
68 line_number_orig
: mismatch
.line_number_orig
,
69 lines_removed
: num_removed
as u32,
70 lines
: new_lines
.collect(),
75 chunks
: chunks
.collect(),
80 // Converts a `Mismatch` into a serialized form, which just includes
81 // enough information to modify the original file.
82 // Each section starts with a line with three integers, space separated:
83 // lineno num_removed num_added
84 // followed by (`num_added`) lines of added text. The line numbers are
85 // relative to the original file.
86 impl fmt
::Display
for ModifiedLines
{
87 fn fmt(&self, f
: &mut fmt
::Formatter
<'_
>) -> fmt
::Result
{
88 for chunk
in &self.chunks
{
92 chunk
.line_number_orig
,
97 for line
in &chunk
.lines
{
98 writeln
!(f
, "{}", line
)?
;
106 // Allows to convert `Display`ed `ModifiedLines` back to the structural data.
107 impl std
::str::FromStr
for ModifiedLines
{
110 fn from_str(s
: &str) -> Result
<ModifiedLines
, ()> {
111 let mut chunks
= vec
![];
113 let mut lines
= s
.lines();
114 while let Some(header
) = lines
.next() {
115 let mut header
= header
.split_whitespace();
116 let (orig
, rem
, new_lines
) = match (header
.next(), header
.next(), header
.next()) {
117 (Some(orig
), Some(removed
), Some(added
)) => (orig
, removed
, added
),
120 let (orig
, rem
, new_lines
): (u32, u32, usize) =
121 match (orig
.parse(), rem
.parse(), new_lines
.parse()) {
122 (Ok(a
), Ok(b
), Ok(c
)) => (a
, b
, c
),
125 let lines
= lines
.by_ref().take(new_lines
);
126 let lines
: Vec
<_
> = lines
.map(ToOwned
::to_owned
).collect();
127 if lines
.len() != new_lines
{
131 chunks
.push(ModifiedChunk
{
132 line_number_orig
: orig
,
138 Ok(ModifiedLines { chunks }
)
142 // This struct handles writing output to stdout and abstracts away the logic
143 // of printing in color, if it's possible in the executing environment.
144 pub(crate) struct OutputWriter
{
145 terminal
: Option
<Box
<dyn term
::Terminal
<Output
= io
::Stdout
>>>,
149 // Create a new OutputWriter instance based on the caller's preference
150 // for colorized output and the capabilities of the terminal.
151 pub(crate) fn new(color
: Color
) -> Self {
152 if let Some(t
) = term
::stdout() {
153 if color
.use_colored_tty() && t
.supports_color() {
154 return OutputWriter { terminal: Some(t) }
;
157 OutputWriter { terminal: None }
160 // Write output in the optionally specified color. The output is written
161 // in the specified color if this OutputWriter instance contains a
162 // Terminal in its `terminal` field.
163 pub(crate) fn writeln(&mut self, msg
: &str, color
: Option
<term
::color
::Color
>) {
164 match &mut self.terminal
{
166 if let Some(color
) = color
{
167 t
.fg(color
).unwrap();
169 writeln
!(t
, "{}", msg
).unwrap();
174 None
=> println
!("{}", msg
),
179 // Produces a diff between the expected output and actual output of rustfmt.
180 pub(crate) fn make_diff(expected
: &str, actual
: &str, context_size
: usize) -> Vec
<Mismatch
> {
181 let mut line_number
= 1;
182 let mut line_number_orig
= 1;
183 let mut context_queue
: VecDeque
<&str> = VecDeque
::with_capacity(context_size
);
184 let mut lines_since_mismatch
= context_size
+ 1;
185 let mut results
= Vec
::new();
186 let mut mismatch
= Mismatch
::new(0, 0);
188 for result
in diff
::lines(expected
, actual
) {
190 diff
::Result
::Left(str) => {
191 if lines_since_mismatch
>= context_size
&& lines_since_mismatch
> 0 {
192 results
.push(mismatch
);
193 mismatch
= Mismatch
::new(
194 line_number
- context_queue
.len() as u32,
195 line_number_orig
- context_queue
.len() as u32,
199 while let Some(line
) = context_queue
.pop_front() {
200 mismatch
.lines
.push(DiffLine
::Context(line
.to_owned()));
203 mismatch
.lines
.push(DiffLine
::Resulting(str.to_owned()));
204 line_number_orig
+= 1;
205 lines_since_mismatch
= 0;
207 diff
::Result
::Right(str) => {
208 if lines_since_mismatch
>= context_size
&& lines_since_mismatch
> 0 {
209 results
.push(mismatch
);
210 mismatch
= Mismatch
::new(
211 line_number
- context_queue
.len() as u32,
212 line_number_orig
- context_queue
.len() as u32,
216 while let Some(line
) = context_queue
.pop_front() {
217 mismatch
.lines
.push(DiffLine
::Context(line
.to_owned()));
220 mismatch
.lines
.push(DiffLine
::Expected(str.to_owned()));
222 lines_since_mismatch
= 0;
224 diff
::Result
::Both(str, _
) => {
225 if context_queue
.len() >= context_size
{
226 let _
= context_queue
.pop_front();
229 if lines_since_mismatch
< context_size
{
230 mismatch
.lines
.push(DiffLine
::Context(str.to_owned()));
231 } else if context_size
> 0 {
232 context_queue
.push_back(str);
236 line_number_orig
+= 1;
237 lines_since_mismatch
+= 1;
242 results
.push(mismatch
);
248 pub(crate) fn print_diff
<F
>(diff
: Vec
<Mismatch
>, get_section_title
: F
, config
: &Config
)
250 F
: Fn(u32) -> String
,
252 let color
= config
.color();
253 let line_terminator
= if config
.verbose() == Verbosity
::Verbose
{
259 let mut writer
= OutputWriter
::new(color
);
261 for mismatch
in diff
{
262 let title
= get_section_title(mismatch
.line_number_orig
);
263 writer
.writeln(&title
, None
);
265 for line
in mismatch
.lines
{
267 DiffLine
::Context(ref str) => {
268 writer
.writeln(&format
!(" {}{}", str, line_terminator
), None
)
270 DiffLine
::Expected(ref str) => writer
.writeln(
271 &format
!("+{}{}", str, line_terminator
),
272 Some(term
::color
::GREEN
),
274 DiffLine
::Resulting(ref str) => writer
.writeln(
275 &format
!("-{}{}", str, line_terminator
),
276 Some(term
::color
::RED
),
285 use super::DiffLine
::*;
286 use super::{make_diff, Mismatch}
;
287 use super::{ModifiedChunk, ModifiedLines}
;
291 let src
= "one\ntwo\nthree\nfour\nfive\n";
292 let dest
= "one\ntwo\ntrois\nfour\nfive\n";
293 let diff
= make_diff(src
, dest
, 1);
300 Context("two".to_owned()),
301 Resulting("three".to_owned()),
302 Expected("trois".to_owned()),
303 Context("four".to_owned()),
311 let src
= "one\ntwo\nthree\nfour\nfive\nsix\nseven\n";
312 let dest
= "one\ntwo\ntrois\nfour\ncinq\nsix\nseven\n";
313 let diff
= make_diff(src
, dest
, 1);
321 Context("two".to_owned()),
322 Resulting("three".to_owned()),
323 Expected("trois".to_owned()),
324 Context("four".to_owned()),
331 Resulting("five".to_owned()),
332 Expected("cinq".to_owned()),
333 Context("six".to_owned()),
341 fn diff_zerocontext() {
342 let src
= "one\ntwo\nthree\nfour\nfive\n";
343 let dest
= "one\ntwo\ntrois\nfour\nfive\n";
344 let diff
= make_diff(src
, dest
, 0);
350 lines
: vec
![Resulting("three".to_owned()), Expected("trois".to_owned())],
356 fn diff_trailing_newline() {
357 let src
= "one\ntwo\nthree\nfour\nfive";
358 let dest
= "one\ntwo\nthree\nfour\nfive\n";
359 let diff
= make_diff(src
, dest
, 1);
365 lines
: vec
![Context("five".to_owned()), Expected("".to_owned())],
371 fn modified_lines_from_str() {
372 use std
::str::FromStr
;
374 let src
= "1 6 2\nfn some() {}\nfn main() {}\n25 3 1\n struct Test {}";
375 let lines
= ModifiedLines
::from_str(src
).unwrap();
383 lines
: vec
!["fn some() {}".to_owned(), "fn main() {}".to_owned(),]
386 line_number_orig
: 25,
388 lines
: vec
![" struct Test {}".to_owned()]
395 assert_eq
!(ModifiedLines
::from_str(src
), Err(()));
397 let src
= "1 5 3\na\nb";
398 assert_eq
!(ModifiedLines
::from_str(src
), Err(()));