#![cfg_attr(doc, doc=concat!("<img width=\"600\" class=\"figure\" src=\"data:image/svg+xml;base64,", include_str!("../plots/linux_comparison.svg.base64"), "\"></img>"))]
use std::ops::Range;
use std::slice;
use crate::util::{strip_common_postfix, strip_common_prefix};
pub use crate::slider_heuristic::{
IndentHeuristic, IndentLevel, NoSliderHeuristic, SliderHeuristic,
};
pub use intern::{InternedInput, Interner, Token, TokenSource};
#[cfg(feature = "unified_diff")]
pub use unified_diff::{BasicLineDiffPrinter, UnifiedDiff, UnifiedDiffConfig, UnifiedDiffPrinter};
mod histogram;
mod intern;
mod myers;
mod postprocess;
mod slider_heuristic;
pub mod sources;
#[cfg(feature = "unified_diff")]
mod unified_diff;
mod util;
#[cfg(test)]
mod tests;
#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
pub enum Algorithm {
#[cfg_attr(doc, doc=concat!("<img width=\"600\" class=\"figure\" src=\"data:image/svg+xml;base64,", include_str!("../plots/linux_speedup.svg.base64"), "\"></img>"))]
#[default]
Histogram,
Myers,
MyersMinimal,
}
impl Algorithm {
#[cfg(test)]
const ALL: [Self; 2] = [Algorithm::Histogram, Algorithm::Myers];
}
#[derive(Default)]
pub struct Diff {
removed: Vec<bool>,
added: Vec<bool>,
}
impl std::fmt::Debug for Diff {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_list().entries(self.hunks()).finish()
}
}
impl Diff {
pub fn compute<T>(algorithm: Algorithm, input: &InternedInput<T>) -> Diff {
let mut diff = Diff::default();
diff.compute_with(
algorithm,
&input.before,
&input.after,
input.interner.num_tokens(),
);
diff
}
pub fn compute_with(
&mut self,
algorithm: Algorithm,
mut before: &[Token],
mut after: &[Token],
num_tokens: u32,
) {
assert!(
before.len() < i32::MAX as usize,
"imara-diff only supports up to {} tokens",
i32::MAX
);
assert!(
after.len() < i32::MAX as usize,
"imara-diff only supports up to {} tokens",
i32::MAX
);
self.removed.clear();
self.added.clear();
self.removed.resize(before.len(), false);
self.added.resize(after.len(), false);
let common_prefix = strip_common_prefix(&mut before, &mut after) as usize;
let common_postfix = strip_common_postfix(&mut before, &mut after);
let range = common_prefix..self.removed.len() - common_postfix as usize;
let removed = &mut self.removed[range];
let range = common_prefix..self.added.len() - common_postfix as usize;
let added = &mut self.added[range];
match algorithm {
Algorithm::Histogram => histogram::diff(before, after, removed, added, num_tokens),
Algorithm::Myers => myers::diff(before, after, removed, added, false),
Algorithm::MyersMinimal => myers::diff(before, after, removed, added, true),
}
}
pub fn count_additions(&self) -> u32 {
self.added.iter().map(|&added| added as u32).sum()
}
pub fn count_removals(&self) -> u32 {
self.removed.iter().map(|&removed| removed as u32).sum()
}
pub fn is_removed(&self, token_idx: u32) -> bool {
self.removed[token_idx as usize]
}
pub fn is_added(&self, token_idx: u32) -> bool {
self.added[token_idx as usize]
}
pub fn postprocess_no_heuristic<T>(&mut self, input: &InternedInput<T>) {
self.postprocess_with_heuristic(input, NoSliderHeuristic)
}
pub fn postprocess_lines<T: AsRef<[u8]>>(&mut self, input: &InternedInput<T>) {
self.postprocess_with_heuristic(
input,
IndentHeuristic::new(|token| {
IndentLevel::for_ascii_line(input.interner[token].as_ref().iter().copied(), 8)
}),
)
}
pub fn hunks(&self) -> HunkIter<'_> {
HunkIter {
removed: self.removed.iter(),
added: self.added.iter(),
pos_before: 0,
pos_after: 0,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
pub struct Hunk {
pub before: Range<u32>,
pub after: Range<u32>,
}
impl Hunk {
pub const NONE: Hunk = Hunk {
before: u32::MAX..u32::MAX,
after: u32::MAX..u32::MAX,
};
pub fn invert(&self) -> Hunk {
Hunk {
before: self.after.clone(),
after: self.before.clone(),
}
}
pub fn is_pure_insertion(&self) -> bool {
self.before.is_empty()
}
pub fn is_pure_removal(&self) -> bool {
self.after.is_empty()
}
}
pub struct HunkIter<'diff> {
removed: slice::Iter<'diff, bool>,
added: slice::Iter<'diff, bool>,
pos_before: u32,
pos_after: u32,
}
impl Iterator for HunkIter<'_> {
type Item = Hunk;
fn next(&mut self) -> Option<Self::Item> {
loop {
let removed = (&mut self.removed).take_while(|&&removed| removed).count() as u32;
let added = (&mut self.added).take_while(|&&added| added).count() as u32;
if removed != 0 || added != 0 {
let start_before = self.pos_before;
let start_after = self.pos_after;
self.pos_before += removed;
self.pos_after += added;
let hunk = Hunk {
before: start_before..self.pos_before,
after: start_after..self.pos_after,
};
self.pos_before += 1;
self.pos_after += 1;
return Some(hunk);
} else if self.removed.len() == 0 && self.added.len() == 0 {
return None;
} else {
self.pos_before += 1;
self.pos_after += 1;
}
}
}
}