use std::ops::Range;
pub mod file_filter;
use crate::DiffHunkHeader;
#[derive(PartialEq, Clone, Copy, Debug, Default)]
#[cfg_attr(docsrs, doc(cfg(feature = "file-changes")))]
pub enum LinesChangedOnly {
#[default]
Off,
Diff,
On,
}
impl LinesChangedOnly {
pub(crate) fn is_change_valid(&self, added_lines: bool, diff_hunks: bool) -> bool {
match self {
LinesChangedOnly::Off => true,
LinesChangedOnly::Diff => diff_hunks,
LinesChangedOnly::On => added_lines,
}
}
}
impl std::fmt::Display for LinesChangedOnly {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
LinesChangedOnly::Off => write!(f, "false"),
LinesChangedOnly::Diff => write!(f, "diff"),
LinesChangedOnly::On => write!(f, "true"),
}
}
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(docsrs, doc(cfg(feature = "file-changes")))]
pub struct FileDiffLines {
pub added_lines: Vec<u32>,
pub added_ranges: Vec<Range<u32>>,
pub diff_hunks: Vec<Range<u32>>,
}
impl FileDiffLines {
pub fn with_info(added_lines: Vec<u32>, diff_chunks: Vec<Range<u32>>) -> Self {
let added_ranges = Self::consolidate_numbers_to_ranges(&added_lines);
Self {
added_lines,
added_ranges,
diff_hunks: diff_chunks,
}
}
fn consolidate_numbers_to_ranges(lines: &[u32]) -> Vec<Range<u32>> {
let mut iter_lines = lines.iter().enumerate();
if let Some((_, start)) = iter_lines.next() {
let mut range_start = *start;
let mut ranges: Vec<Range<u32>> = Vec::new();
let last_entry = lines.len() - 1;
for (index, number) in iter_lines {
if let Some(prev) = lines.get(index - 1)
&& (number - 1) != *prev
{
ranges.push(range_start..(*prev + 1));
range_start = *number;
}
if index == last_entry {
ranges.push(range_start..(*number + 1));
}
}
ranges
} else {
Vec::new()
}
}
pub fn get_ranges(&self, lines_changed_only: &LinesChangedOnly) -> Option<Vec<Range<u32>>> {
match lines_changed_only {
LinesChangedOnly::Diff => Some(self.diff_hunks.to_vec()),
LinesChangedOnly::On => Some(self.added_ranges.to_vec()),
_ => None,
}
}
pub fn is_hunk_in_diff(&self, hunk: &DiffHunkHeader) -> Option<(u32, u32)> {
let (start_line, end_line) = if hunk.old_lines > 0 {
let start = hunk.old_start;
(start, start + hunk.old_lines)
} else {
let start = hunk.new_start;
(start, start + 1)
};
let inclusive_end = end_line - 1;
for range in &self.diff_hunks {
if range.contains(&start_line) && range.contains(&inclusive_end) {
return Some((start_line, end_line));
}
}
None
}
pub fn is_line_in_diff(&self, line: &u32) -> bool {
for range in &self.diff_hunks {
if range.contains(line) {
return true;
}
}
false
}
}
#[cfg(test)]
mod test {
use super::{FileDiffLines, LinesChangedOnly};
#[test]
fn display_lines_changed_only() {
assert_eq!(LinesChangedOnly::Off.to_string(), "false");
assert_eq!(LinesChangedOnly::Diff.to_string(), "diff");
assert_eq!(LinesChangedOnly::On.to_string(), "true");
}
#[test]
fn get_ranges_none() {
let file_obj = FileDiffLines::default();
let ranges = file_obj.get_ranges(&LinesChangedOnly::Off);
assert!(ranges.is_none());
}
#[test]
fn get_ranges_diff() {
#[allow(clippy::single_range_in_vec_init)]
let diff_chunks = vec![1..11];
let added_lines = vec![4, 5, 9];
let file_obj = FileDiffLines::with_info(added_lines, diff_chunks.clone());
let ranges = file_obj.get_ranges(&LinesChangedOnly::Diff);
assert_eq!(ranges.unwrap(), diff_chunks);
}
#[test]
fn get_ranges_added() {
#[allow(clippy::single_range_in_vec_init)]
let diff_chunks = vec![1..11];
let added_lines = vec![4, 5, 9];
let file_obj = FileDiffLines::with_info(added_lines, diff_chunks);
let ranges = file_obj.get_ranges(&LinesChangedOnly::On);
assert_eq!(ranges.unwrap(), vec![4..6, 9..10]);
}
#[test]
fn line_not_in_diff() {
let file_obj = FileDiffLines::default();
assert!(!file_obj.is_line_in_diff(&42));
}
}