Store novel lines on hunks

This allows us to recognise lines that have only additions or only
removals, even if we've managed to find a matching opposite line.

Fixes #115
html_output 0.18.0
Wilfred Hughes 2022-01-30 17:44:15 +07:00
parent ea24dd8784
commit 9b77d07e93
2 changed files with 50 additions and 5 deletions

@ -10,6 +10,7 @@ use std::collections::{HashMap, HashSet};
use crate::{ use crate::{
context::{add_context, opposite_positions, MAX_PADDING}, context::{add_context, opposite_positions, MAX_PADDING},
lines::LineNumber, lines::LineNumber,
side_by_side::lines_with_novel,
syntax::{zip_pad_shorter, MatchedPos}, syntax::{zip_pad_shorter, MatchedPos},
}; };
@ -17,6 +18,8 @@ use crate::{
/// together. /// together.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Hunk { pub struct Hunk {
pub novel_lhs: HashSet<LineNumber>,
pub novel_rhs: HashSet<LineNumber>,
pub lines: Vec<(Option<LineNumber>, Option<LineNumber>)>, pub lines: Vec<(Option<LineNumber>, Option<LineNumber>)>,
} }
@ -57,6 +60,8 @@ impl Hunk {
} }
Hunk { Hunk {
novel_lhs: self.novel_lhs.union(&other.novel_lhs).copied().collect(),
novel_rhs: self.novel_rhs.union(&other.novel_rhs).copied().collect(),
lines: deduped_lines, lines: deduped_lines,
} }
} }
@ -261,8 +266,38 @@ fn enforce_increasing(
res res
} }
fn find_novel_lines(
lines: &[(Option<LineNumber>, Option<LineNumber>)],
all_lhs_novel: &HashSet<LineNumber>,
all_rhs_novel: &HashSet<LineNumber>,
) -> (HashSet<LineNumber>, HashSet<LineNumber>) {
let mut lhs_novel = HashSet::new();
let mut rhs_novel = HashSet::new();
for (lhs_line, rhs_line) in lines {
if let Some(lhs_line) = lhs_line {
if all_lhs_novel.contains(lhs_line) {
lhs_novel.insert(*lhs_line);
}
}
if let Some(rhs_line) = rhs_line {
if all_rhs_novel.contains(rhs_line) {
rhs_novel.insert(*rhs_line);
}
}
}
(lhs_novel, rhs_novel)
}
/// Split lines into hunks. /// Split lines into hunks.
fn lines_to_hunks(lines: &[(Option<LineNumber>, Option<LineNumber>)]) -> Vec<Hunk> { fn lines_to_hunks(
lines: &[(Option<LineNumber>, Option<LineNumber>)],
lhs_mps: &[MatchedPos],
rhs_mps: &[MatchedPos],
) -> Vec<Hunk> {
let (all_lhs_novel, all_rhs_novel) = lines_with_novel(lhs_mps, rhs_mps);
let mut hunks = vec![]; let mut hunks = vec![];
let mut current_hunk_lines = vec![]; let mut current_hunk_lines = vec![];
let mut max_lhs_line: Option<LineNumber> = None; let mut max_lhs_line: Option<LineNumber> = None;
@ -274,7 +309,11 @@ fn lines_to_hunks(lines: &[(Option<LineNumber>, Option<LineNumber>)]) -> Vec<Hun
if current_hunk_lines.is_empty() || lines_are_close(max_lhs_line, max_rhs_line, line) { if current_hunk_lines.is_empty() || lines_are_close(max_lhs_line, max_rhs_line, line) {
current_hunk_lines.push(line); current_hunk_lines.push(line);
} else { } else {
let (novel_lhs, novel_rhs) =
find_novel_lines(&current_hunk_lines, &all_lhs_novel, &all_rhs_novel);
hunks.push(Hunk { hunks.push(Hunk {
novel_lhs,
novel_rhs,
lines: current_hunk_lines, lines: current_hunk_lines,
}); });
current_hunk_lines = vec![line]; current_hunk_lines = vec![line];
@ -289,7 +328,11 @@ fn lines_to_hunks(lines: &[(Option<LineNumber>, Option<LineNumber>)]) -> Vec<Hun
} }
if !current_hunk_lines.is_empty() { if !current_hunk_lines.is_empty() {
let (novel_lhs, novel_rhs) =
find_novel_lines(&current_hunk_lines, &all_lhs_novel, &all_rhs_novel);
hunks.push(Hunk { hunks.push(Hunk {
novel_lhs,
novel_rhs,
lines: current_hunk_lines, lines: current_hunk_lines,
}); });
} }
@ -463,7 +506,7 @@ fn matched_novel_lines(
} }
pub fn matched_pos_to_hunks(lhs_mps: &[MatchedPos], rhs_mps: &[MatchedPos]) -> Vec<Hunk> { pub fn matched_pos_to_hunks(lhs_mps: &[MatchedPos], rhs_mps: &[MatchedPos]) -> Vec<Hunk> {
lines_to_hunks(&matched_novel_lines(lhs_mps, rhs_mps)) lines_to_hunks(&matched_novel_lines(lhs_mps, rhs_mps), lhs_mps, rhs_mps)
} }
/// Ensure that we don't miss any intermediate values. /// Ensure that we don't miss any intermediate values.

@ -177,7 +177,7 @@ impl Widths {
} }
} }
fn lines_with_novel( pub fn lines_with_novel(
lhs_mps: &[MatchedPos], lhs_mps: &[MatchedPos],
rhs_mps: &[MatchedPos], rhs_mps: &[MatchedPos],
) -> (HashSet<LineNumber>, HashSet<LineNumber>) { ) -> (HashSet<LineNumber>, HashSet<LineNumber>) {
@ -296,8 +296,8 @@ pub fn display_hunks(
)); ));
let aligned_lines = matched_lines_for_hunk(&matched_lines, hunk); let aligned_lines = matched_lines_for_hunk(&matched_lines, hunk);
let no_lhs_changes = hunk.lines.iter().all(|(l, _)| l.is_none()); let no_lhs_changes = hunk.novel_lhs.is_empty();
let no_rhs_changes = hunk.lines.iter().all(|(_, r)| r.is_none()); let no_rhs_changes = hunk.novel_rhs.is_empty();
let same_lines = aligned_lines.iter().all(|(l, r)| l == r); let same_lines = aligned_lines.iter().all(|(l, r)| l == r);
let widths = Widths::new(display_width, &aligned_lines, &lhs_lines, &rhs_lines); let widths = Widths::new(display_width, &aligned_lines, &lhs_lines, &rhs_lines);
@ -532,6 +532,8 @@ mod tests {
}]; }];
let hunks = [Hunk { let hunks = [Hunk {
novel_lhs: HashSet::from([0.into()]),
novel_rhs: HashSet::from([0.into()]),
lines: vec![(Some(0.into()), Some(0.into()))], lines: vec![(Some(0.into()), Some(0.into()))],
}]; }];