Ensure matched lines includes blanks at the ends of the file

Fixes #163
pull/185/head
Wilfred Hughes 2022-03-20 22:31:32 +07:00
parent 905fc9ec8b
commit 9e32e2e08e
4 changed files with 160 additions and 4 deletions

@ -6,6 +6,9 @@ Fixed an issue where hunks would be missing lines. This occurred in
certain circumstances when a line contained both changed and unchanged
parts.
Fixed an issue where blank lines at the beginning or end of a file
would be excluded from context.
Fixed an issue where lines containing only whitespace would be
highlighted in purple.

@ -1,5 +1,5 @@
sample_files/bad_combine_before.rs sample_files/bad_combine_after.rs
ffb31b7f11712d0e28a7f874bb7756c6 -
8cdee5b915c20cb561d53148be0d1e2c -
sample_files/change_outer_before.el sample_files/change_outer_after.el
1e144818ad54091b05e58e2c617add8f -
@ -14,7 +14,7 @@ sample_files/context_before.rs sample_files/context_after.rs
5f8518317cbaa2920c9d8c86d060c84f -
sample_files/contiguous_before.js sample_files/contiguous_after.js
b5e718eb8328f8df8aff0eddf4e46d1a -
2efffebe782b66dbf458375d763bf88d -
sample_files/css_before.css sample_files/css_after.css
a110f9a9ab8586f2f307df3cc9997ead -
@ -74,7 +74,7 @@ sample_files/nesting_before.el sample_files/nesting_after.el
c9b74f137aada068b0a317c09966e705 -
sample_files/ocaml_before.ml sample_files/ocaml_after.ml
10f583899eee701776e2b25d96e78b56 -
cbab85e13fa5c630a2425870931e183d -
sample_files/outer_delimiter_before.el sample_files/outer_delimiter_after.el
7b7f8b78e7b9544473b1a5c55abc3372 -

@ -22,7 +22,7 @@ pub fn all_matched_lines_filled(
lhs_lines: &[&str],
rhs_lines: &[&str],
) -> Vec<(Option<LineNumber>, Option<LineNumber>)> {
let matched_lines = all_matched_lines(lhs_mps, rhs_mps);
let matched_lines = add_ends(&all_matched_lines(lhs_mps, rhs_mps), lhs_lines, rhs_lines);
compact_gaps(&ensure_contiguous(&match_preceding_blanks(
&matched_lines,
@ -31,6 +31,90 @@ pub fn all_matched_lines_filled(
)))
}
/// Extend `matched_lines` to include the leading and trailing lines
/// in the file.
///
/// If the leading or trailing lines are blank, we won't have any
/// MatchedPos values corresponding with those lines.
fn add_ends(
matched_lines: &[(Option<LineNumber>, Option<LineNumber>)],
lhs_lines: &[&str],
rhs_lines: &[&str],
) -> Vec<(Option<LineNumber>, Option<LineNumber>)> {
let mut lhs_min = None;
let mut rhs_min = None;
for (lhs_line, rhs_line) in matched_lines {
if lhs_min.is_none() && lhs_line.is_some() {
lhs_min = *lhs_line;
}
if rhs_min.is_none() && rhs_line.is_some() {
rhs_min = *rhs_line;
}
if lhs_min.is_some() && rhs_min.is_some() {
break;
}
}
let mut lhs_max = None;
let mut rhs_max = None;
for (lhs_line, rhs_line) in matched_lines.iter().rev() {
if lhs_max.is_none() && lhs_line.is_some() {
lhs_max = *lhs_line;
}
if rhs_max.is_none() && rhs_line.is_some() {
rhs_max = *rhs_line;
}
if lhs_max.is_some() && rhs_max.is_some() {
break;
}
}
let mut res = vec![];
if let (Some(lhs_min), Some(rhs_min)) = (lhs_min, rhs_min) {
let mut lhs_line: LineNumber = 0.into();
let mut rhs_line: LineNumber = 0.into();
while lhs_line < lhs_min && rhs_line < rhs_min {
res.push((Some(lhs_line), Some(rhs_line)));
lhs_line = (lhs_line.0 + 1).into();
rhs_line = (rhs_line.0 + 1).into();
}
while lhs_line < lhs_min {
res.push((Some(lhs_line), None));
lhs_line = (lhs_line.0 + 1).into();
}
while rhs_line < rhs_min {
res.push((None, Some(rhs_line)));
rhs_line = (rhs_line.0 + 1).into();
}
}
res.extend_from_slice(matched_lines);
if let (Some(lhs_max), Some(rhs_max)) = (lhs_max, rhs_max) {
let mut lhs_line: LineNumber = (lhs_max.0 + 1).into();
let mut rhs_line: LineNumber = (rhs_max.0 + 1).into();
while lhs_line.0 < lhs_lines.len() && rhs_line.0 < rhs_lines.len() {
res.push((Some(lhs_line), Some(rhs_line)));
lhs_line = (lhs_line.0 + 1).into();
rhs_line = (rhs_line.0 + 1).into();
}
while lhs_line.0 < lhs_lines.len() {
res.push((Some(lhs_line), None));
lhs_line = (lhs_line.0 + 1).into();
}
while rhs_line.0 < rhs_lines.len() {
res.push((None, Some(rhs_line)));
rhs_line = (rhs_line.0 + 1).into();
}
}
res
}
fn all_matched_lines(
lhs_mps: &[MatchedPos],
rhs_mps: &[MatchedPos],
@ -908,4 +992,43 @@ mod tests {
]
);
}
#[test]
fn test_matched_lines_blank_at_ends() {
let lhs_lines = vec!["", "foo", ""];
let rhs_lines = vec!["", "foo", ""];
let matched_pos = SingleLineSpan {
line: 1.into(),
start_col: 2,
end_col: 3,
};
let lhs_mps = [MatchedPos {
kind: MatchKind::UnchangedToken {
highlight: TokenKind::Delimiter,
self_pos: vec![matched_pos],
opposite_pos: vec![matched_pos],
},
pos: matched_pos,
}];
let rhs_mps = [MatchedPos {
kind: MatchKind::UnchangedToken {
highlight: TokenKind::Delimiter,
self_pos: vec![matched_pos],
opposite_pos: vec![matched_pos],
},
pos: matched_pos,
}];
let res = all_matched_lines_filled(&lhs_mps, &rhs_mps, &lhs_lines, &rhs_lines);
assert_eq!(
res,
vec![
(Some(0.into()), Some(0.into())),
(Some(1.into()), Some(1.into())),
(Some(2.into()), Some(2.into()))
]
);
}
}

@ -130,6 +130,34 @@ fn diff_slice_by_hash<'a, T: Eq + Hash>(
.collect::<Vec<_>>()
}
fn split_unchanged_ends<'a>(
lhs_lines: &[&'a str],
rhs_lines: &[&'a str],
) -> (Vec<&'a str>, Vec<&'a str>) {
let mut lhs_lines = lhs_lines;
let mut rhs_lines = rhs_lines;
while let (Some(lhs_line), Some(rhs_line)) = (lhs_lines.first(), rhs_lines.first()) {
if lhs_line == rhs_line {
lhs_lines = &lhs_lines[1..];
rhs_lines = &rhs_lines[1..];
} else {
break;
}
}
while let (Some(lhs_line), Some(rhs_line)) = (lhs_lines.last(), rhs_lines.last()) {
if lhs_line == rhs_line {
lhs_lines = &lhs_lines[..lhs_lines.len() - 1];
rhs_lines = &rhs_lines[..rhs_lines.len() - 1];
} else {
break;
}
}
(Vec::from(lhs_lines), Vec::from(rhs_lines))
}
fn changed_parts<'a>(
src: &'a str,
opposite_src: &'a str,
@ -137,6 +165,8 @@ fn changed_parts<'a>(
let src_lines = split_lines_keep_newline(src);
let opposite_src_lines = split_lines_keep_newline(opposite_src);
let (src_lines, opposite_src_lines) = split_unchanged_ends(&src_lines, &opposite_src_lines);
let mut res: Vec<(TextChangeKind, Vec<&'a str>, Vec<&'a str>)> = vec![];
for diff_res in diff_slice_by_hash(&src_lines, &opposite_src_lines) {
match diff_res {