Track the corresponding line in the alternate file

pull/2/head
Wilfred Hughes 2019-01-26 18:00:06 +07:00
parent 5a92de425d
commit 522e24f178
3 changed files with 128 additions and 54 deletions

@ -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<Change>) {
@ -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<Range> {
pub fn added(differences: &[Change]) -> Vec<Change> {
differences
.iter()
.filter(|c| c.kind == ChangeKind::Add)
.map(|c| c.range)
.map(|c| *c)
.collect()
}
pub fn removed(differences: &[Change]) -> Vec<Range> {
pub fn removed(differences: &[Change]) -> Vec<Change> {
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);

@ -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<LineNumber> {
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<LineNumber> {
let mut result: Vec<LineNumber> = 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<MatchedLine> {
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<MatchedLine> {
let mut result: Vec<MatchedLine> = 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));

@ -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<usize> {
}
/// 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<usize> =
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);