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
use std::collections::VecDeque;
use colored::Colorize;
use diff;
use std::fmt;
use std::ops::Range;

pub fn print_diff(filename: &str, left: &str, right: &str) {
    println!("{}", format!("--- {}", filename).red());
    println!("{}", format!("+++ {}", filename).green());
    for chunk in chunked_diff(left, right, 3) {
        println!("{}", chunk);
    }
}

struct Chunk<'a> {
    lines: Vec<diff::Result<&'a str>>,
    left_range: Range<usize>,
    right_range: Range<usize>,
}

impl<'a> Chunk<'a> {
    fn new() -> Chunk<'a> {
        Chunk {
            lines: Vec::new(),
            left_range: 0..0,
            right_range: 0..0,
        }
    }
}

impl<'a> fmt::Display for Chunk<'a> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        writeln!(
            f,
            "{}",
            format!(
                "@@ -{},{} +{},{} @@",
                self.left_range.start,
                self.left_range.len(),
                self.right_range.start,
                self.right_range.len()
            ).cyan()
        )?;
        for line in &self.lines {
            match *line {
                diff::Result::Left(l) => writeln!(f, "{}", format!("-{}", l).red())?,
                diff::Result::Right(r) => writeln!(f, "{}", format!("+{}", r).green())?,
                diff::Result::Both(l, _) => writeln!(f, " {}", l)?,
            }
        }
        Ok(())
    }
}

fn chunked_diff<'a>(left: &'a str, right: &'a str, context: usize) -> Vec<Chunk<'a>> {
    let mut chunks = Vec::new();
    let mut recent_common = VecDeque::new();
    let mut after_context_remaining = 0;
    let mut chunk = Chunk::new();
    let mut left_line_num = 1;
    let mut right_line_num = 1;
    for diff in diff::lines(left, right) {
        let line_delta = match diff {
            diff::Result::Left(_) => (1, 0),
            diff::Result::Right(_) => (0, 1),
            diff::Result::Both(_, _) => (1, 1),
        };
        left_line_num += line_delta.0;
        right_line_num += line_delta.1;
        match diff {
            diff::Result::Left(_) | diff::Result::Right(_) => {
                if chunk.lines.is_empty() {
                    chunk.left_range =
                        left_line_num - recent_common.len() - line_delta.0..left_line_num;
                    chunk.right_range =
                        right_line_num - recent_common.len() - line_delta.1..right_line_num;
                }
                chunk.left_range.end = left_line_num;
                chunk.right_range.end = right_line_num;
                chunk.lines.extend(recent_common.drain(0..));
                chunk.lines.push(diff);
                after_context_remaining = context;
            }
            diff::Result::Both(_, _) => if after_context_remaining > 0 {
                chunk.lines.push(diff);
                chunk.left_range.end = left_line_num;
                chunk.right_range.end = right_line_num;
                after_context_remaining -= 1;
            } else {
                recent_common.push_back(diff);
                if recent_common.len() > context {
                    if !chunk.lines.is_empty() {
                        chunks.push(chunk);
                        chunk = Chunk::new();
                    }
                    recent_common.pop_front();
                }
            },
        }
    }
    if !chunk.lines.is_empty() {
        chunks.push(chunk);
    }
    chunks
}

#[cfg(test)]
mod tests {
    use super::*;
    use colored;

    #[test]
    fn changed_at_start() {
        let chunks = chunked_diff("1\n2\n3\n", "3\n", 2);
        assert_eq!(chunks.len(), 1);
        colored::control::set_override(false);
        assert_eq!(format!("{}", chunks[0]), "@@ -1,3 +1,1 @@\n-1\n-2\n 3\n");
    }

    #[test]
    fn changed_at_end() {
        let chunks = chunked_diff("1\n2\n3\n", "1\n2\n", 2);
        assert_eq!(chunks.len(), 1);
        colored::control::set_override(false);
        assert_eq!(format!("{}", chunks[0]), "@@ -1,3 +1,2 @@\n 1\n 2\n-3\n");
    }

    #[test]
    fn two_chunks() {
        let chunks = chunked_diff("1\n2\n3\n4\n5\n", "2\n3\n4\n", 1);
        assert_eq!(chunks.len(), 2);
        colored::control::set_override(false);
        assert_eq!(format!("{}", chunks[0]), "@@ -1,2 +1,1 @@\n-1\n 2\n");
        assert_eq!(format!("{}", chunks[1]), "@@ -4,2 +3,1 @@\n 4\n-5\n");
    }
}