use std::fmt;
use std::ops::{Index, Range};
use crate::algorithms::utils::is_empty_range;
use crate::algorithms::DiffHook;
use crate::iter::ChangesIter;
#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub enum Algorithm {
Myers,
Patience,
Lcs,
}
impl Default for Algorithm {
fn default() -> Algorithm {
Algorithm::Myers
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Ord, PartialOrd)]
pub enum ChangeTag {
Equal,
Delete,
Insert,
}
impl fmt::Display for ChangeTag {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}",
match &self {
ChangeTag::Equal => ' ',
ChangeTag::Delete => '-',
ChangeTag::Insert => '+',
}
)
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Ord, PartialOrd)]
pub struct Change<'s, T: ?Sized> {
pub(crate) tag: ChangeTag,
pub(crate) old_index: Option<usize>,
pub(crate) new_index: Option<usize>,
pub(crate) value: &'s T,
}
impl<'s, T: ?Sized> Change<'s, T> {
pub fn tag(&self) -> ChangeTag {
self.tag
}
pub fn old_index(&self) -> Option<usize> {
self.old_index
}
pub fn new_index(&self) -> Option<usize> {
self.new_index
}
pub fn value(&self) -> &'s T {
self.value
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum DiffOp {
Equal {
old_index: usize,
new_index: usize,
len: usize,
},
Delete {
old_index: usize,
old_len: usize,
new_index: usize,
},
Insert {
old_index: usize,
new_index: usize,
new_len: usize,
},
Replace {
old_index: usize,
old_len: usize,
new_index: usize,
new_len: usize,
},
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Ord, PartialOrd)]
pub enum DiffTag {
Equal,
Delete,
Insert,
Replace,
}
impl DiffOp {
pub fn tag(self) -> DiffTag {
self.as_tag_tuple().0
}
pub fn old_range(&self) -> Range<usize> {
self.as_tag_tuple().1
}
pub fn new_range(&self) -> Range<usize> {
self.as_tag_tuple().2
}
pub fn as_tag_tuple(&self) -> (DiffTag, Range<usize>, Range<usize>) {
match *self {
DiffOp::Equal {
old_index,
new_index,
len,
} => (
DiffTag::Equal,
old_index..old_index + len,
new_index..new_index + len,
),
DiffOp::Delete {
old_index,
new_index,
old_len,
} => (
DiffTag::Delete,
old_index..old_index + old_len,
new_index..new_index,
),
DiffOp::Insert {
old_index,
new_index,
new_len,
} => (
DiffTag::Insert,
old_index..old_index,
new_index..new_index + new_len,
),
DiffOp::Replace {
old_index,
old_len,
new_index,
new_len,
} => (
DiffTag::Replace,
old_index..old_index + old_len,
new_index..new_index + new_len,
),
}
}
pub fn apply_to_hook<D: DiffHook>(&self, d: &mut D) -> Result<(), D::Error> {
match *self {
DiffOp::Equal {
old_index,
new_index,
len,
} => d.equal(old_index, new_index, len),
DiffOp::Delete {
old_index,
old_len,
new_index,
} => d.delete(old_index, old_len, new_index),
DiffOp::Insert {
old_index,
new_index,
new_len,
} => d.insert(old_index, new_index, new_len),
DiffOp::Replace {
old_index,
old_len,
new_index,
new_len,
} => d.replace(old_index, old_len, new_index, new_len),
}
}
pub fn iter_changes<'x, 'lookup, Old, New, T>(
&self,
old: &'lookup Old,
new: &'lookup New,
) -> ChangesIter<'lookup, 'x, Old, New, T>
where
Old: Index<usize, Output = &'x T> + ?Sized,
New: Index<usize, Output = &'x T> + ?Sized,
T: 'x + ?Sized,
'x: 'lookup,
{
ChangesIter::new(old, new, *self)
}
pub fn iter_slices<'lookup, Old, New, T>(
&self,
old: &'lookup Old,
new: &'lookup New,
) -> impl Iterator<Item = (ChangeTag, &'lookup T)>
where
T: 'lookup + ?Sized,
Old: Index<Range<usize>, Output = T> + ?Sized,
New: Index<Range<usize>, Output = T> + ?Sized,
{
match *self {
DiffOp::Equal { old_index, len, .. } => {
Some((ChangeTag::Equal, &old[old_index..old_index + len]))
.into_iter()
.chain(None.into_iter())
}
DiffOp::Insert {
new_index, new_len, ..
} => Some((ChangeTag::Insert, &new[new_index..new_index + new_len]))
.into_iter()
.chain(None.into_iter()),
DiffOp::Delete {
old_index, old_len, ..
} => Some((ChangeTag::Delete, &old[old_index..old_index + old_len]))
.into_iter()
.chain(None.into_iter()),
DiffOp::Replace {
old_index,
old_len,
new_index,
new_len,
} => Some((ChangeTag::Delete, &old[old_index..old_index + old_len]))
.into_iter()
.chain(Some((ChangeTag::Insert, &new[new_index..new_index + new_len])).into_iter()),
}
}
pub(crate) fn is_empty(&self) -> bool {
let (_, old, new) = self.as_tag_tuple();
is_empty_range(&old) && is_empty_range(&new)
}
pub(crate) fn shift_left(&mut self, adjust: usize) {
self.adjust((adjust, true), (0, false));
}
pub(crate) fn shift_right(&mut self, adjust: usize) {
self.adjust((adjust, false), (0, false));
}
pub(crate) fn grow_left(&mut self, adjust: usize) {
self.adjust((adjust, true), (adjust, false));
}
pub(crate) fn grow_right(&mut self, adjust: usize) {
self.adjust((0, false), (adjust, false));
}
pub(crate) fn shrink_left(&mut self, adjust: usize) {
self.adjust((0, false), (adjust, true));
}
pub(crate) fn shrink_right(&mut self, adjust: usize) {
self.adjust((adjust, false), (adjust, true));
}
fn adjust(&mut self, adjust_offset: (usize, bool), adjust_len: (usize, bool)) {
#[inline(always)]
fn modify(val: &mut usize, adj: (usize, bool)) {
if adj.1 {
*val -= adj.0;
} else {
*val += adj.0;
}
}
match self {
DiffOp::Equal {
old_index,
new_index,
len,
} => {
modify(old_index, adjust_offset);
modify(new_index, adjust_offset);
modify(len, adjust_len);
}
DiffOp::Delete {
old_index,
old_len,
new_index,
} => {
modify(old_index, adjust_offset);
modify(old_len, adjust_len);
modify(new_index, adjust_offset);
}
DiffOp::Insert {
old_index,
new_index,
new_len,
} => {
modify(old_index, adjust_offset);
modify(new_index, adjust_offset);
modify(new_len, adjust_len);
}
DiffOp::Replace {
old_index,
old_len,
new_index,
new_len,
} => {
modify(old_index, adjust_offset);
modify(old_len, adjust_len);
modify(new_index, adjust_offset);
modify(new_len, adjust_len);
}
}
}
}
#[cfg(feature = "text")]
mod text_additions {
use super::*;
use crate::text::DiffableStr;
use std::borrow::Cow;
impl<'s, T: DiffableStr + ?Sized> Change<'s, T> {
pub fn as_str(&self) -> Option<&'s str> {
T::as_str(self.value)
}
pub fn to_string_lossy(&self) -> Cow<'s, str> {
T::to_string_lossy(self.value)
}
pub fn missing_newline(&self) -> bool {
!T::ends_with_newline(self.value)
}
}
impl<'s, T: DiffableStr + ?Sized> fmt::Display for Change<'s, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}{}",
self.to_string_lossy(),
if self.missing_newline() { "\n" } else { "" }
)
}
}
}