summaryrefslogtreecommitdiff
path: root/cli/diff.rs
blob: 7510ebfa9b8f0759d9f32110f5d5ea790d9e75ad (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.

use crate::colors;
use dissimilar::{diff as difference, Chunk};
use std::fmt;
use std::fmt::Write;

fn fmt_add() -> String {
  format!("{}", colors::green_bold("+"))
}

fn fmt_add_text(x: &str) -> String {
  format!("{}", colors::green(x))
}

fn fmt_add_text_highlight(x: &str) -> String {
  format!("{}", colors::black_on_green(x))
}

fn fmt_rem() -> String {
  format!("{}", colors::red_bold("-"))
}

fn fmt_rem_text(x: &str) -> String {
  format!("{}", colors::red(x))
}

fn fmt_rem_text_highlight(x: &str) -> String {
  format!("{}", colors::white_on_red(x))
}

fn write_line_diff(
  diff: &mut String,
  orig_line: &mut usize,
  edit_line: &mut usize,
  line_number_width: usize,
  orig: &mut String,
  edit: &mut String,
) -> fmt::Result {
  let split = orig.split('\n').enumerate();
  for (i, s) in split {
    write!(
      diff,
      "{:width$}{} ",
      *orig_line + i,
      colors::gray(" |"),
      width = line_number_width
    )?;
    write!(diff, "{}", fmt_rem())?;
    write!(diff, "{}", s)?;
    writeln!(diff)?;
  }

  let split = edit.split('\n').enumerate();
  for (i, s) in split {
    write!(
      diff,
      "{:width$}{} ",
      *edit_line + i,
      colors::gray(" |"),
      width = line_number_width
    )?;
    write!(diff, "{}", fmt_add())?;
    write!(diff, "{}", s)?;
    writeln!(diff)?;
  }

  *orig_line += orig.split('\n').count();
  *edit_line += edit.split('\n').count();

  orig.clear();
  edit.clear();

  Ok(())
}

/// Print diff of the same file_path, before and after formatting.
///
/// Diff format is loosely based on Github diff formatting.
pub fn diff(orig_text: &str, edit_text: &str) -> Result<String, fmt::Error> {
  let lines = edit_text.split('\n').count();
  let line_number_width = lines.to_string().chars().count();

  let mut diff = String::new();

  let mut text1 = orig_text.to_string();
  let mut text2 = edit_text.to_string();

  if !text1.ends_with('\n') {
    writeln!(text1)?;
  }
  if !text2.ends_with('\n') {
    writeln!(text2)?;
  }

  let mut orig_line: usize = 1;
  let mut edit_line: usize = 1;
  let mut orig: String = String::new();
  let mut edit: String = String::new();
  let mut changes = false;

  let chunks = difference(&text1, &text2);
  for chunk in chunks {
    match chunk {
      Chunk::Delete(s) => {
        let split = s.split('\n').enumerate();
        for (i, s) in split {
          if i > 0 {
            orig.push('\n');
          }
          orig.push_str(&fmt_rem_text_highlight(s));
        }
        changes = true
      }
      Chunk::Insert(s) => {
        let split = s.split('\n').enumerate();
        for (i, s) in split {
          if i > 0 {
            edit.push('\n');
          }
          edit.push_str(&fmt_add_text_highlight(s));
        }
        changes = true
      }
      Chunk::Equal(s) => {
        let split = s.split('\n').enumerate();
        for (i, s) in split {
          if i > 0 {
            if changes {
              write_line_diff(
                &mut diff,
                &mut orig_line,
                &mut edit_line,
                line_number_width,
                &mut orig,
                &mut edit,
              )?;
              changes = false
            } else {
              orig.clear();
              edit.clear();
              orig_line += 1;
              edit_line += 1;
            }
          }
          orig.push_str(&fmt_rem_text(s));
          edit.push_str(&fmt_add_text(s));
        }
      }
    }
  }
  Ok(diff)
}

#[test]
fn test_diff() {
  let simple_console_log_unfmt = "console.log('Hello World')";
  let simple_console_log_fmt = "console.log(\"Hello World\");";
  assert_eq!(
    colors::strip_ansi_codes(
      &diff(simple_console_log_unfmt, simple_console_log_fmt).unwrap()
    ),
    "1 | -console.log('Hello World')\n1 | +console.log(\"Hello World\");\n"
  );

  let line_number_unfmt = "\n\n\n\nconsole.log(\n'Hello World'\n)";
  let line_number_fmt = "console.log(\n\"Hello World\"\n);";
  assert_eq!(
    colors::strip_ansi_codes(&diff(line_number_unfmt, line_number_fmt).unwrap()),
    "1 | -\n2 | -\n3 | -\n4 | -\n5 | -console.log(\n1 | +console.log(\n6 | -'Hello World'\n2 | +\"Hello World\"\n7 | -)\n3 | +);\n"
  )
}