diff --git a/src/diffs.rs b/src/diffs.rs index ab9486556..37ff5a452 100644 --- a/src/diffs.rs +++ b/src/diffs.rs @@ -1,6 +1,6 @@ use colored::*; use language::{language_lexer, lex, Language}; -use lines::{LineRange, NewlinePositions, Range}; +use lines::{LineNumber, LineRange, NewlinePositions, Range}; use std::cmp::min; use std::collections::HashMap; @@ -11,10 +11,14 @@ pub enum ChangeKind { } /// An addition or removal in a string. +#[derive(Debug, PartialEq, Clone, Copy)] pub struct Change { kind: ChangeKind, /// The position of the affected text. - range: Range, + pub range: Range, + /// The corresponding position of the comparison string. This + /// enables us to line up changed lines between the two strings. + pub opposite_line: LineNumber, } pub fn difference_positions(before_src: &str, after_src: &str, lang: Language) -> (Vec) { @@ -22,7 +26,13 @@ pub fn difference_positions(before_src: &str, after_src: &str, lang: Language) - let before_tokens = lex(&before_src, &re); let after_tokens = lex(&after_src, &re); + let before_newlines = NewlinePositions::from(before_src); + let after_newlines = NewlinePositions::from(after_src); + + let mut left_line = LineNumber::from(0); + let mut right_line = LineNumber::from(0); let mut positions = vec![]; + for d in diff::slice(&before_tokens, &after_tokens) { match d { // Only present in the before, so has been removed. @@ -33,10 +43,14 @@ pub fn difference_positions(before_src: &str, after_src: &str, lang: Language) - start: l.start, end: l.start + l.text.len(), }, + opposite_line: right_line }); } // Present in both. - diff::Result::Both(_, _) => (), + diff::Result::Both(l, r) => { + left_line = before_newlines.from_offset(l.start).line; + right_line = after_newlines.from_offset(r.start).line; + } // Only present in the after. diff::Result::Right(r) => { positions.push(Change { @@ -45,6 +59,7 @@ pub fn difference_positions(before_src: &str, after_src: &str, lang: Language) - start: r.start, end: r.start + r.text.len(), }, + opposite_line: left_line }); } } @@ -157,19 +172,19 @@ fn apply_color_overlapping_end() { ); } -pub fn added(differences: &[Change]) -> Vec { +pub fn added(differences: &[Change]) -> Vec { differences .iter() .filter(|c| c.kind == ChangeKind::Add) - .map(|c| c.range) + .map(|c| *c) .collect() } -pub fn removed(differences: &[Change]) -> Vec { +pub fn removed(differences: &[Change]) -> Vec { differences .iter() .filter(|c| c.kind == ChangeKind::Remove) - .map(|c| c.range) + .map(|c| *c) .collect() } @@ -181,8 +196,11 @@ pub fn highlight_differences( let before_newlines = NewlinePositions::from(before_src); let after_newlines = NewlinePositions::from(after_src); - let before_ranges = before_newlines.from_ranges(&removed(differences)); - let after_ranges = after_newlines.from_ranges(&added(differences)); + let before_abs_ranges: Vec<_> = removed(differences).iter().map(|c| c.range).collect(); + let after_abs_ranges: Vec<_> = added(differences).iter().map(|c| c.range).collect(); + + let before_ranges = before_newlines.from_ranges(&before_abs_ranges); + let after_ranges = after_newlines.from_ranges(&after_abs_ranges); let before_colored = apply_color_by_line(&before_src, &before_ranges, Color::Red); let after_colored = apply_color_by_line(&after_src, &after_ranges, Color::Green); diff --git a/src/lines.rs b/src/lines.rs index 60f17ff9b..3616a3610 100644 --- a/src/lines.rs +++ b/src/lines.rs @@ -1,4 +1,6 @@ +use std::collections::HashSet; use regex::Regex; +use diffs::Change; use std::cmp::{max, min}; // TODO: Move to a separate file, this isn't line related. @@ -22,9 +24,9 @@ impl LineNumber { /// A position in a single line of a string. #[derive(Debug, PartialEq, Clone, Copy)] -struct LinePosition { +pub struct LinePosition { /// Both zero-indexed. - line: LineNumber, + pub line: LineNumber, column: usize, } @@ -58,7 +60,7 @@ impl NewlinePositions { } } - fn from_offset(self: &NewlinePositions, offset: usize) -> LinePosition { + pub fn from_offset(self: &NewlinePositions, offset: usize) -> LinePosition { for line_num in (0..self.positions.len()).rev() { if offset > self.positions[line_num as usize] { return LinePosition { @@ -172,32 +174,68 @@ fn from_ranges_split_over_multiple_lines() { ); } -/// Given a slice of absolute positioned ranges, return the lines that -/// they land on. -pub fn relevant_lines(ranges: &[Range], s: &str) -> Vec { - NewlinePositions::from(s) - .from_ranges(ranges) - .iter() - .map(|r| r.line) - .collect() +#[derive(Debug, PartialEq)] +pub struct MatchedLine { + pub line: LineNumber, + pub opposite_line: LineNumber } -pub fn add_context(lines: &[LineNumber], context: usize, max_line: LineNumber) -> Vec { - let mut result: Vec = vec![]; +/// Given a slice of changes, return the unique lines that +/// they land on, plus their corresponding line in the other file. +pub fn relevant_lines(changes: &[Change], s: &str) -> Vec { + let newlines = NewlinePositions::from(s); - for line in lines { - let earliest = max(0, line.number as isize - context as isize) as usize; - let latest = min(line.number + context, max_line.number); + let mut line_nums_seen = HashSet::new(); + + let mut result = vec![]; + for change in changes { + // TODO: refactor to from_range. + let line_relative_ranges = newlines.from_ranges(&[change.range]); + for range in line_relative_ranges { + if line_nums_seen.contains(&range.line) { + continue; + } + + line_nums_seen.insert(range.line); + result.push(MatchedLine { + line: range.line, + opposite_line: change.opposite_line + }); + } + } + + result +} + +pub fn add_context(lines: &[MatchedLine], context: usize, max_line: LineNumber) -> Vec { + let mut result: Vec = vec![]; + + for matched_line in lines { + // We know the line number that matches this line. In order to + // calculate the opposite line number for the context lines, + // we assume that they line up. Context line -1 should have + // opposite_line - 1. + let opposite_offset = matched_line.opposite_line.number as isize - matched_line.line.number as isize; + + let line_number = matched_line.line.number; + let earliest = max(0, line_number as isize - context as isize) as usize; + let latest = min(line_number + context, max_line.number); for i in earliest..latest + 1 { let mut is_new = true; if let Some(last_line) = result.last() { - if i <= last_line.number { + if i <= last_line.line.number { is_new = false; } } if is_new { - result.push(LineNumber::from(i)); + result.push( + MatchedLine { + line: LineNumber::from(i), + opposite_line: LineNumber::from(max(i as isize + opposite_offset, 0) as usize) + } + + ); } } } @@ -212,26 +250,33 @@ pub fn max_line(s: &str) -> LineNumber { #[test] fn test_add_context() { + fn matched_line(i: usize) -> MatchedLine { + MatchedLine { + line: LineNumber::from(i), + opposite_line: LineNumber::from(i), + } + } + let start_lines = [ - LineNumber::from(5), - LineNumber::from(12), - LineNumber::from(14), + matched_line(5), + matched_line(12), + matched_line(14), ]; let result = add_context(&start_lines, 2, LineNumber::from(20)); let expected = [ - LineNumber::from(3), - LineNumber::from(4), - LineNumber::from(5), - LineNumber::from(6), - LineNumber::from(7), - LineNumber::from(10), - LineNumber::from(11), - LineNumber::from(12), - LineNumber::from(13), - LineNumber::from(14), - LineNumber::from(15), - LineNumber::from(16), + matched_line(3), + matched_line(4), + matched_line(5), + matched_line(6), + matched_line(7), + matched_line(10), + matched_line(11), + matched_line(12), + matched_line(13), + matched_line(14), + matched_line(15), + matched_line(16), ]; assert_eq!(result, expected); } @@ -239,9 +284,18 @@ fn test_add_context() { #[test] fn test_add_zero_context() { let start_lines = [ - LineNumber::from(5), - LineNumber::from(12), - LineNumber::from(14), + MatchedLine { + line: LineNumber::from(5), + opposite_line: LineNumber::from(5), + }, + MatchedLine { + line: LineNumber::from(12), + opposite_line: LineNumber::from(12), + }, + MatchedLine { + line: LineNumber::from(14), + opposite_line: LineNumber::from(14), + } ]; let result = add_context(&start_lines, 0, LineNumber::from(20)); diff --git a/src/main.rs b/src/main.rs index 8a3fd905d..d6250c828 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,7 +14,7 @@ use diffs::{added, difference_positions, highlight_differences, removed}; use itertools::EitherOrBoth::{Both, Left, Right}; use itertools::Itertools; use language::{infer_language, Language}; -use lines::{add_context, enforce_length, max_line, relevant_lines, LineNumber}; +use lines::{add_context, enforce_length, max_line, relevant_lines, MatchedLine}; use std::collections::HashSet; use std::fs; use std::iter::FromIterator; @@ -24,9 +24,9 @@ fn term_width() -> Option { } /// Return a copy of string that only contains the lines specified. -fn filter_lines(s: &str, lines_wanted: &[LineNumber]) -> String { +fn filter_lines(s: &str, lines_wanted: &[MatchedLine]) -> String { let lines_wanted: HashSet = - HashSet::from_iter(lines_wanted.iter().map(|line| line.number)); + HashSet::from_iter(lines_wanted.iter().map(|ml| ml.line.number)); let mut result = String::new(); let mut first = true; @@ -45,8 +45,15 @@ fn filter_lines(s: &str, lines_wanted: &[LineNumber]) -> String { #[test] fn test_filter_lines() { + fn matched_line(i: usize) -> MatchedLine { + MatchedLine { + line: LineNumber::from(i), + opposite_line: LineNumber::from(i), + } + } + let s = "foo\nbar\nbaz\nquux"; - let result = filter_lines(s, &[LineNumber::from(1), LineNumber::from(3)]); + let result = filter_lines(s, &[matched_line(1), matched_line(3)]); assert_eq!(result, "bar\nquux"); } @@ -125,12 +132,7 @@ fn main() { let differences = difference_positions(&before_src, &after_src, language); let mut left_lines = relevant_lines(&removed(&differences), &before_src); - left_lines.sort(); - left_lines.dedup(); - let mut right_lines = relevant_lines(&added(&differences), &after_src); - right_lines.sort(); - right_lines.dedup(); let (mut before_colored, mut after_colored) = highlight_differences(&before_src, &after_src, &differences);