use std::collections::{HashMap, HashSet};
use rustc_hash::FxHashSet;
use crate::{
hunks::{compact_gaps, ensure_contiguous},
lines::LineNumber,
syntax::{zip_repeat_shorter, MatchKind, MatchedPos},
};
pub const MAX_PADDING: usize = 3;
pub fn all_matched_lines_filled(
lhs_mps: &[MatchedPos],
rhs_mps: &[MatchedPos],
) -> Vec<(Option<LineNumber>, Option<LineNumber>)> {
let matched_lines = all_matched_lines(lhs_mps, rhs_mps);
compact_gaps(&ensure_contiguous(&matched_lines))
}
fn all_matched_lines(
lhs_mps: &[MatchedPos],
rhs_mps: &[MatchedPos],
) -> Vec<(Option<LineNumber>, Option<LineNumber>)> {
let lhs_matched_lines = matched_lines_from_unchanged(lhs_mps);
let rhs_lines = all_lines(rhs_mps);
let lines = merge_in_opposite_lines(&lhs_matched_lines, &rhs_lines);
let lhs_lines = all_lines(lhs_mps);
flip_tuples(&merge_in_opposite_lines(&flip_tuples(&lines), &lhs_lines))
}
fn all_lines(mps: &[MatchedPos]) -> Vec<LineNumber> {
let mut lines = FxHashSet::default();
for mp in mps {
lines.insert(mp.pos.line);
}
let mut res: Vec<LineNumber> = lines.into_iter().collect();
res.sort_unstable();
res
}
fn matched_lines_from_unchanged(
mps: &[MatchedPos],
) -> Vec<(Option<LineNumber>, Option<LineNumber>)> {
let mut highest_line = None;
let mut highest_opposite_line = None;
let mut res: Vec<(Option<LineNumber>, Option<LineNumber>)> = vec![];
for mp in mps {
let opposite_line = match &mp.kind {
MatchKind::UnchangedToken { opposite_pos, .. }
| MatchKind::NovelLinePart { opposite_pos, .. } => {
if let Some(highest_opposite_side) = highest_opposite_line {
opposite_pos
.iter()
.map(|p| p.line)
.find(|l| *l > highest_opposite_side)
} else {
opposite_pos.first().map(|p| p.line)
}
}
MatchKind::Novel { .. } | MatchKind::NovelWord { .. } => None,
};
let should_insert = match highest_line {
Some(highest_this_side) => mp.pos.line > highest_this_side,
None => true,
};
if should_insert && opposite_line.is_some() {
res.push((Some(mp.pos.line), opposite_line));
highest_line = Some(mp.pos.line);
if opposite_line.is_some() {
highest_opposite_line = opposite_line;
}
}
}
res
}
fn merge_in_opposite_lines(
matched_lines: &[(Option<LineNumber>, Option<LineNumber>)],
novel_opposite_lines: &[LineNumber],
) -> Vec<(Option<LineNumber>, Option<LineNumber>)> {
let mut res: Vec<(Option<LineNumber>, Option<LineNumber>)> = vec![];
let mut i = 0;
for (line, opposite_line) in matched_lines {
if let Some(opposite_line) = opposite_line {
while let Some(novel_opposite_line) = novel_opposite_lines.get(i) {
if novel_opposite_line < opposite_line {
res.push((None, Some(*novel_opposite_line)));
i += 1;
} else if novel_opposite_line == opposite_line {
i += 1;
} else {
break;
}
}
}
res.push((*line, *opposite_line));
}
while let Some(novel_opposite_line) = novel_opposite_lines.get(i) {
res.push((None, Some(*novel_opposite_line)));
i += 1;
}
res
}
pub fn opposite_positions(mps: &[MatchedPos]) -> HashMap<LineNumber, HashSet<LineNumber>> {
let mut res: HashMap<LineNumber, HashSet<LineNumber>> = HashMap::new();
for mp in mps {
match &mp.kind {
MatchKind::UnchangedToken {
self_pos,
opposite_pos,
..
} => {
for (self_span, opposite_span) in zip_repeat_shorter(self_pos, opposite_pos) {
let opposite_lines = res.entry(self_span.line).or_insert_with(HashSet::new);
opposite_lines.insert(opposite_span.line);
}
}
MatchKind::NovelLinePart {
opposite_pos,
self_pos,
..
} => {
let opposite_lines = res.entry(self_pos.line).or_insert_with(HashSet::new);
for opposite_span in opposite_pos {
opposite_lines.insert(opposite_span.line);
}
}
MatchKind::Novel { .. } | MatchKind::NovelWord { .. } => {}
}
}
res
}
fn before_with_opposites(
before_lines: &[LineNumber],
opposite_lines: &HashMap<LineNumber, HashSet<LineNumber>>,
) -> Vec<(Option<LineNumber>, Option<LineNumber>)> {
let mut lines = before_lines.to_vec();
lines.reverse();
let mut prev_opposite: Option<LineNumber> = None;
let mut res = vec![];
for line in lines {
let current_opposite: Option<LineNumber> = match prev_opposite {
Some(prev_opposite) => {
if prev_opposite.0 > 0 {
Some((prev_opposite.0 - 1).into())
} else {
None
}
}
None => match opposite_lines.get(&line) {
Some(all_opposites) => {
let mut all_opposites: Vec<LineNumber> =
all_opposites.iter().copied().collect();
all_opposites.sort();
all_opposites.last().copied()
}
None => None,
},
};
res.push((Some(line), current_opposite));
if current_opposite.is_some() {
prev_opposite = current_opposite;
}
}
res.reverse();
res
}
fn pad_before(ln: LineNumber) -> Vec<LineNumber> {
let mut res = vec![];
let mut current = ln;
for _ in 0..MAX_PADDING + 1 {
if current.0 == 0 {
break;
}
current = (current.0 - 1).into();
res.push(current);
}
res.reverse();
res
}
fn pad_after(ln: LineNumber, max_line: LineNumber) -> Vec<LineNumber> {
let mut res = vec![];
let mut current = ln;
for _ in 0..MAX_PADDING + 1 {
if current == max_line {
break;
}
current = (current.0 + 1).into();
res.push(current);
}
res
}
pub fn flip_tuple<Tx: Copy, Ty: Copy>(pair: (Tx, Ty)) -> (Ty, Tx) {
let (x, y) = pair;
(y, x)
}
pub fn flip_tuples<Tx: Copy, Ty: Copy>(items: &[(Tx, Ty)]) -> Vec<(Ty, Tx)> {
items.iter().copied().map(flip_tuple).collect()
}
fn after_with_opposites(
after_lines: &[LineNumber],
opposite_lines: &HashMap<LineNumber, HashSet<LineNumber>>,
prev_max_opposite: Option<LineNumber>,
max_opposite: LineNumber,
) -> Vec<(Option<LineNumber>, Option<LineNumber>)> {
let mut prev_opposite: Option<LineNumber> = None;
let mut res: Vec<(Option<LineNumber>, Option<LineNumber>)> = vec![];
for line in after_lines {
let current_opposite: Option<LineNumber> = match prev_opposite {
Some(prev_opposite) => {
if prev_opposite < max_opposite {
Some((prev_opposite.0 + 1).into())
} else {
None
}
}
None => match opposite_lines.get(line) {
Some(all_opposites) => {
let mut all_opposites: Vec<LineNumber> =
all_opposites.iter().copied().collect();
all_opposites.sort();
if let Some(prev_max_opposite) = prev_max_opposite {
all_opposites = all_opposites
.into_iter()
.filter(|x| *x > prev_max_opposite)
.collect()
}
all_opposites.first().copied()
}
None => None,
},
};
res.push((Some(*line), current_opposite));
if current_opposite.is_some() {
prev_opposite = current_opposite;
}
}
res
}
pub fn calculate_before_context(
lines: &[(Option<LineNumber>, Option<LineNumber>)],
opposite_to_lhs: &HashMap<LineNumber, HashSet<LineNumber>>,
opposite_to_rhs: &HashMap<LineNumber, HashSet<LineNumber>>,
) -> Vec<(Option<LineNumber>, Option<LineNumber>)> {
match lines.first() {
Some(first_line) => match *first_line {
(Some(lhs_line), _) => {
let padded_lines = pad_before(lhs_line);
before_with_opposites(&padded_lines, opposite_to_lhs)
}
(_, Some(rhs_line)) => {
let padded_lines = pad_before(rhs_line);
flip_tuples(&before_with_opposites(&padded_lines, opposite_to_rhs))
}
(None, None) => vec![],
},
None => vec![],
}
}
pub fn calculate_after_context(
lines: &[(Option<LineNumber>, Option<LineNumber>)],
opposite_to_lhs: &HashMap<LineNumber, HashSet<LineNumber>>,
opposite_to_rhs: &HashMap<LineNumber, HashSet<LineNumber>>,
max_lhs_src_line: LineNumber,
max_rhs_src_line: LineNumber,
) -> Vec<(Option<LineNumber>, Option<LineNumber>)> {
match lines.last() {
Some(first_line) => match *first_line {
(Some(lhs_line), _) => {
let mut max_opposite = None;
for (_, rhs_line) in lines {
if let Some(rhs_line) = rhs_line {
max_opposite = Some(*rhs_line);
}
}
let padded_lines = pad_after(lhs_line, max_lhs_src_line);
after_with_opposites(
&padded_lines,
opposite_to_lhs,
max_opposite,
max_rhs_src_line,
)
}
(_, Some(rhs_line)) => {
let mut max_opposite = None;
for (lhs_line, _) in lines {
if let Some(lhs_line) = lhs_line {
max_opposite = Some(*lhs_line);
}
}
let padded_lines = pad_after(rhs_line, max_rhs_src_line);
flip_tuples(&after_with_opposites(
&padded_lines,
opposite_to_rhs,
max_opposite,
max_lhs_src_line,
))
}
(None, None) => vec![],
},
None => vec![],
}
}
pub fn add_context(
lines: &[(Option<LineNumber>, Option<LineNumber>)],
opposite_to_lhs: &HashMap<LineNumber, HashSet<LineNumber>>,
opposite_to_rhs: &HashMap<LineNumber, HashSet<LineNumber>>,
max_lhs_src_line: LineNumber,
max_rhs_src_line: LineNumber,
) -> Vec<(Option<LineNumber>, Option<LineNumber>)> {
let before_lines = calculate_before_context(lines, opposite_to_lhs, opposite_to_rhs);
let after_lines = calculate_after_context(
&[&before_lines, lines].concat(),
opposite_to_lhs,
opposite_to_rhs,
max_lhs_src_line,
max_rhs_src_line,
);
before_lines
.into_iter()
.chain(lines.iter().copied())
.chain(after_lines.into_iter())
.collect()
}
#[cfg(test)]
mod tests {
use std::iter::FromIterator;
use crate::{positions::SingleLineSpan, syntax::TokenKind};
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_calculate_before_context() {
let lines = vec![(Some(1.into()), Some(1.into()))];
let mut opposite_to_lhs = HashMap::new();
opposite_to_lhs.insert(0.into(), HashSet::from_iter([0.into()]));
let mut opposite_to_rhs = HashMap::new();
opposite_to_rhs.insert(0.into(), HashSet::from_iter([0.into()]));
let res = calculate_before_context(&lines, &opposite_to_lhs, &opposite_to_rhs);
assert_eq!(res, vec![(Some(0.into()), Some(0.into()))]);
}
#[test]
fn test_all_matched_lines() {
let matched_pos = SingleLineSpan {
line: 1.into(),
start_col: 2,
end_col: 3,
};
let lhs_mps = [
MatchedPos {
kind: MatchKind::Novel {
highlight: TokenKind::Delimiter,
},
pos: SingleLineSpan {
line: 0.into(),
start_col: 1,
end_col: 2,
},
},
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,
}];
assert_eq!(
all_matched_lines(&lhs_mps, &rhs_mps),
vec![(Some(0.into()), None), (Some(1.into()), Some(1.into()))]
);
}
#[test]
fn test_matched_lines_novel_on_same_line() {
let matched_pos = SingleLineSpan {
line: 1.into(),
start_col: 2,
end_col: 3,
};
let mps = [
MatchedPos {
kind: MatchKind::Novel {
highlight: TokenKind::Delimiter,
},
pos: SingleLineSpan {
line: 1.into(),
start_col: 1,
end_col: 2,
},
},
MatchedPos {
kind: MatchKind::UnchangedToken {
highlight: TokenKind::Delimiter,
self_pos: vec![matched_pos],
opposite_pos: vec![matched_pos],
},
pos: matched_pos,
},
];
assert_eq!(
matched_lines_from_unchanged(&mps),
vec![(Some(1.into()), Some(1.into()))]
);
}
#[test]
fn test_all_lines() {
let mps = [
MatchedPos {
kind: MatchKind::NovelLinePart {
highlight: TokenKind::Delimiter,
self_pos: SingleLineSpan {
line: 0.into(),
start_col: 2,
end_col: 3,
},
opposite_pos: vec![SingleLineSpan {
line: 0.into(),
start_col: 2,
end_col: 3,
}],
},
pos: SingleLineSpan {
line: 0.into(),
start_col: 2,
end_col: 3,
},
},
MatchedPos {
kind: MatchKind::UnchangedToken {
highlight: TokenKind::Delimiter,
self_pos: vec![SingleLineSpan {
line: 1.into(),
start_col: 2,
end_col: 3,
}],
opposite_pos: vec![SingleLineSpan {
line: 1.into(),
start_col: 2,
end_col: 3,
}],
},
pos: SingleLineSpan {
line: 1.into(),
start_col: 2,
end_col: 3,
},
},
MatchedPos {
kind: MatchKind::Novel {
highlight: TokenKind::Delimiter,
},
pos: SingleLineSpan {
line: 2.into(),
start_col: 1,
end_col: 2,
},
},
];
assert_eq!(all_lines(&mps), vec![0.into(), 1.into(), 2.into()]);
}
#[test]
fn test_merge_in_opposite_lines() {
let matched_lines = [(Some(1.into()), Some(1.into()))];
let novel_lines = [0.into(), 3.into()];
assert_eq!(
merge_in_opposite_lines(&matched_lines, &novel_lines),
vec![
(None, Some(0.into())),
(Some(1.into()), Some(1.into())),
(None, Some(3.into()))
]
);
}
}