similar/
types.rs

1use std::fmt;
2use std::ops::{Index, Range};
3
4use crate::algorithms::utils::is_empty_range;
5use crate::algorithms::DiffHook;
6use crate::iter::ChangesIter;
7
8/// An enum representing a diffing algorithm.
9#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Debug)]
10#[cfg_attr(
11    feature = "serde",
12    derive(serde::Serialize, serde::Deserialize),
13    serde(rename_all = "snake_case")
14)]
15pub enum Algorithm {
16    /// Picks the myers algorithm from [`crate::algorithms::myers`]
17    Myers,
18    /// Picks the patience algorithm from [`crate::algorithms::patience`]
19    Patience,
20    /// Picks the LCS algorithm from [`crate::algorithms::lcs`]
21    Lcs,
22}
23
24impl Default for Algorithm {
25    /// Returns the default algorithm ([`Algorithm::Myers`]).
26    fn default() -> Algorithm {
27        Algorithm::Myers
28    }
29}
30
31/// The tag of a change.
32#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Ord, PartialOrd)]
33#[cfg_attr(
34    feature = "serde",
35    derive(serde::Serialize, serde::Deserialize),
36    serde(rename_all = "snake_case")
37)]
38pub enum ChangeTag {
39    /// The change indicates equality (not a change)
40    Equal,
41    /// The change indicates deleted text.
42    Delete,
43    /// The change indicates inserted text.
44    Insert,
45}
46
47impl fmt::Display for ChangeTag {
48    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
49        write!(
50            f,
51            "{}",
52            match &self {
53                ChangeTag::Equal => ' ',
54                ChangeTag::Delete => '-',
55                ChangeTag::Insert => '+',
56            }
57        )
58    }
59}
60
61/// Represents the expanded [`DiffOp`] change.
62///
63/// This type is returned from [`DiffOp::iter_changes`] and
64/// [`TextDiff::iter_changes`](crate::text::TextDiff::iter_changes).
65///
66/// It exists so that it's more convenient to work with textual differences as
67/// the underlying [`DiffOp`] encodes a group of changes.
68///
69/// This type has additional methods that are only available for types
70/// implementing [`DiffableStr`](crate::text::DiffableStr).
71#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Ord, PartialOrd)]
72#[cfg_attr(feature = "serde", derive(serde::Serialize))]
73pub struct Change<T> {
74    pub(crate) tag: ChangeTag,
75    pub(crate) old_index: Option<usize>,
76    pub(crate) new_index: Option<usize>,
77    pub(crate) value: T,
78}
79
80/// These methods are available for all change types.
81impl<T: Clone> Change<T> {
82    /// Returns the change tag.
83    pub fn tag(&self) -> ChangeTag {
84        self.tag
85    }
86
87    /// Returns the old index if available.
88    pub fn old_index(&self) -> Option<usize> {
89        self.old_index
90    }
91
92    /// Returns the new index if available.
93    pub fn new_index(&self) -> Option<usize> {
94        self.new_index
95    }
96
97    /// Returns the underlying changed value.
98    ///
99    /// Depending on the type of the underlying [`crate::text::DiffableStr`]
100    /// this value is more or less useful.  If you always want to have a utf-8
101    /// string it's best to use the [`Change::as_str`] and
102    /// [`Change::to_string_lossy`] methods.
103    pub fn value(&self) -> T {
104        self.value.clone()
105    }
106
107    /// Returns the underlying changed value as reference.
108    pub fn value_ref(&self) -> &T {
109        &self.value
110    }
111
112    /// Returns the underlying changed value as mutable reference.
113    pub fn value_mut(&mut self) -> &mut T {
114        &mut self.value
115    }
116}
117
118/// Utility enum to capture a diff operation.
119///
120/// This is used by [`Capture`](crate::algorithms::Capture).
121#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
122#[cfg_attr(
123    feature = "serde",
124    derive(serde::Serialize, serde::Deserialize),
125    serde(rename_all = "snake_case", tag = "op")
126)]
127pub enum DiffOp {
128    /// A segment is equal (see [`DiffHook::equal`])
129    Equal {
130        /// The starting index in the old sequence.
131        old_index: usize,
132        /// The starting index in the new sequence.
133        new_index: usize,
134        /// The length of the segment.
135        len: usize,
136    },
137    /// A segment was deleted (see [`DiffHook::delete`])
138    Delete {
139        /// The starting index in the old sequence.
140        old_index: usize,
141        /// The length of the old segment.
142        old_len: usize,
143        /// The starting index in the new sequence.
144        new_index: usize,
145    },
146    /// A segment was inserted (see [`DiffHook::insert`])
147    Insert {
148        /// The starting index in the old sequence.
149        old_index: usize,
150        /// The starting index in the new sequence.
151        new_index: usize,
152        /// The length of the new segment.
153        new_len: usize,
154    },
155    /// A segment was replaced (see [`DiffHook::replace`])
156    Replace {
157        /// The starting index in the old sequence.
158        old_index: usize,
159        /// The length of the old segment.
160        old_len: usize,
161        /// The starting index in the new sequence.
162        new_index: usize,
163        /// The length of the new segment.
164        new_len: usize,
165    },
166}
167
168/// The tag of a diff operation.
169#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Ord, PartialOrd)]
170#[cfg_attr(
171    feature = "serde",
172    derive(serde::Serialize, serde::Deserialize),
173    serde(rename_all = "snake_case")
174)]
175pub enum DiffTag {
176    /// The diff op encodes an equal segment.
177    Equal,
178    /// The diff op encodes a deleted segment.
179    Delete,
180    /// The diff op encodes an inserted segment.
181    Insert,
182    /// The diff op encodes a replaced segment.
183    Replace,
184}
185
186impl DiffOp {
187    /// Returns the tag of the operation.
188    pub fn tag(self) -> DiffTag {
189        self.as_tag_tuple().0
190    }
191
192    /// Returns the old range.
193    pub fn old_range(&self) -> Range<usize> {
194        self.as_tag_tuple().1
195    }
196
197    /// Returns the new range.
198    pub fn new_range(&self) -> Range<usize> {
199        self.as_tag_tuple().2
200    }
201
202    /// Transform the op into a tuple of diff tag and ranges.
203    ///
204    /// This is useful when operating on slices.  The returned format is
205    /// `(tag, i1..i2, j1..j2)`:
206    ///
207    /// * `Replace`: `a[i1..i2]` should be replaced by `b[j1..j2]`
208    /// * `Delete`: `a[i1..i2]` should be deleted (`j1 == j2` in this case).
209    /// * `Insert`: `b[j1..j2]` should be inserted at `a[i1..i2]` (`i1 == i2` in this case).
210    /// * `Equal`: `a[i1..i2]` is equal to `b[j1..j2]`.
211    pub fn as_tag_tuple(&self) -> (DiffTag, Range<usize>, Range<usize>) {
212        match *self {
213            DiffOp::Equal {
214                old_index,
215                new_index,
216                len,
217            } => (
218                DiffTag::Equal,
219                old_index..old_index + len,
220                new_index..new_index + len,
221            ),
222            DiffOp::Delete {
223                old_index,
224                new_index,
225                old_len,
226            } => (
227                DiffTag::Delete,
228                old_index..old_index + old_len,
229                new_index..new_index,
230            ),
231            DiffOp::Insert {
232                old_index,
233                new_index,
234                new_len,
235            } => (
236                DiffTag::Insert,
237                old_index..old_index,
238                new_index..new_index + new_len,
239            ),
240            DiffOp::Replace {
241                old_index,
242                old_len,
243                new_index,
244                new_len,
245            } => (
246                DiffTag::Replace,
247                old_index..old_index + old_len,
248                new_index..new_index + new_len,
249            ),
250        }
251    }
252
253    /// Apply this operation to a diff hook.
254    pub fn apply_to_hook<D: DiffHook>(&self, d: &mut D) -> Result<(), D::Error> {
255        match *self {
256            DiffOp::Equal {
257                old_index,
258                new_index,
259                len,
260            } => d.equal(old_index, new_index, len),
261            DiffOp::Delete {
262                old_index,
263                old_len,
264                new_index,
265            } => d.delete(old_index, old_len, new_index),
266            DiffOp::Insert {
267                old_index,
268                new_index,
269                new_len,
270            } => d.insert(old_index, new_index, new_len),
271            DiffOp::Replace {
272                old_index,
273                old_len,
274                new_index,
275                new_len,
276            } => d.replace(old_index, old_len, new_index, new_len),
277        }
278    }
279
280    /// Iterates over all changes encoded in the diff op against old and new
281    /// sequences.
282    ///
283    /// `old` and `new` are two indexable objects like the types you pass to
284    /// the diffing algorithm functions.
285    ///
286    /// ```rust
287    /// use similar::{ChangeTag, Algorithm};
288    /// use similar::capture_diff_slices;
289    /// let old = vec!["foo", "bar", "baz"];
290    /// let new = vec!["foo", "bar", "blah"];
291    /// let ops = capture_diff_slices(Algorithm::Myers, &old, &new);
292    /// let changes: Vec<_> = ops
293    ///     .iter()
294    ///     .flat_map(|x| x.iter_changes(&old, &new))
295    ///     .map(|x| (x.tag(), x.value()))
296    ///     .collect();
297    /// assert_eq!(changes, vec![
298    ///     (ChangeTag::Equal, "foo"),
299    ///     (ChangeTag::Equal, "bar"),
300    ///     (ChangeTag::Delete, "baz"),
301    ///     (ChangeTag::Insert, "blah"),
302    /// ]);
303    /// ```
304    pub fn iter_changes<'lookup, Old, New, T>(
305        &self,
306        old: &'lookup Old,
307        new: &'lookup New,
308    ) -> ChangesIter<'lookup, Old, New, T>
309    where
310        Old: Index<usize, Output = T> + ?Sized,
311        New: Index<usize, Output = T> + ?Sized,
312    {
313        ChangesIter::new(old, new, *self)
314    }
315
316    /// Given a diffop yields the changes it encodes against the given slices.
317    ///
318    /// This is similar to [`DiffOp::iter_changes`] but instead of yielding the
319    /// individual changes it yields consequitive changed slices.
320    ///
321    /// This will only ever yield a single tuple or two tuples in case a
322    /// [`DiffOp::Replace`] operation is passed.
323    ///
324    /// ```rust
325    /// use similar::{ChangeTag, Algorithm};
326    /// use similar::capture_diff_slices;
327    /// let old = vec!["foo", "bar", "baz"];
328    /// let new = vec!["foo", "bar", "blah"];
329    /// let ops = capture_diff_slices(Algorithm::Myers, &old, &new);
330    /// let changes: Vec<_> = ops.iter().flat_map(|x| x.iter_slices(&old, &new)).collect();
331    /// assert_eq!(changes, vec![
332    ///     (ChangeTag::Equal, &["foo", "bar"][..]),
333    ///     (ChangeTag::Delete, &["baz"][..]),
334    ///     (ChangeTag::Insert, &["blah"][..]),
335    /// ]);
336    /// ```
337    ///
338    /// Due to lifetime restrictions it's currently impossible for the
339    /// returned slices to outlive the lookup.
340    pub fn iter_slices<'lookup, Old, New, T>(
341        &self,
342        old: &'lookup Old,
343        new: &'lookup New,
344    ) -> impl Iterator<Item = (ChangeTag, &'lookup T)>
345    where
346        T: 'lookup + ?Sized,
347        Old: Index<Range<usize>, Output = T> + ?Sized,
348        New: Index<Range<usize>, Output = T> + ?Sized,
349    {
350        match *self {
351            DiffOp::Equal { old_index, len, .. } => {
352                Some((ChangeTag::Equal, &old[old_index..old_index + len]))
353                    .into_iter()
354                    .chain(None)
355            }
356            DiffOp::Insert {
357                new_index, new_len, ..
358            } => Some((ChangeTag::Insert, &new[new_index..new_index + new_len]))
359                .into_iter()
360                .chain(None),
361            DiffOp::Delete {
362                old_index, old_len, ..
363            } => Some((ChangeTag::Delete, &old[old_index..old_index + old_len]))
364                .into_iter()
365                .chain(None),
366            DiffOp::Replace {
367                old_index,
368                old_len,
369                new_index,
370                new_len,
371            } => Some((ChangeTag::Delete, &old[old_index..old_index + old_len]))
372                .into_iter()
373                .chain(Some((
374                    ChangeTag::Insert,
375                    &new[new_index..new_index + new_len],
376                ))),
377        }
378    }
379
380    pub(crate) fn is_empty(&self) -> bool {
381        let (_, old, new) = self.as_tag_tuple();
382        is_empty_range(&old) && is_empty_range(&new)
383    }
384
385    pub(crate) fn shift_left(&mut self, adjust: usize) {
386        self.adjust((adjust, true), (0, false));
387    }
388
389    pub(crate) fn shift_right(&mut self, adjust: usize) {
390        self.adjust((adjust, false), (0, false));
391    }
392
393    pub(crate) fn grow_left(&mut self, adjust: usize) {
394        self.adjust((adjust, true), (adjust, false));
395    }
396
397    pub(crate) fn grow_right(&mut self, adjust: usize) {
398        self.adjust((0, false), (adjust, false));
399    }
400
401    pub(crate) fn shrink_left(&mut self, adjust: usize) {
402        self.adjust((0, false), (adjust, true));
403    }
404
405    pub(crate) fn shrink_right(&mut self, adjust: usize) {
406        self.adjust((adjust, false), (adjust, true));
407    }
408
409    fn adjust(&mut self, adjust_offset: (usize, bool), adjust_len: (usize, bool)) {
410        #[inline(always)]
411        fn modify(val: &mut usize, adj: (usize, bool)) {
412            if adj.1 {
413                *val -= adj.0;
414            } else {
415                *val += adj.0;
416            }
417        }
418
419        match self {
420            DiffOp::Equal {
421                old_index,
422                new_index,
423                len,
424            } => {
425                modify(old_index, adjust_offset);
426                modify(new_index, adjust_offset);
427                modify(len, adjust_len);
428            }
429            DiffOp::Delete {
430                old_index,
431                old_len,
432                new_index,
433            } => {
434                modify(old_index, adjust_offset);
435                modify(old_len, adjust_len);
436                modify(new_index, adjust_offset);
437            }
438            DiffOp::Insert {
439                old_index,
440                new_index,
441                new_len,
442            } => {
443                modify(old_index, adjust_offset);
444                modify(new_index, adjust_offset);
445                modify(new_len, adjust_len);
446            }
447            DiffOp::Replace {
448                old_index,
449                old_len,
450                new_index,
451                new_len,
452            } => {
453                modify(old_index, adjust_offset);
454                modify(old_len, adjust_len);
455                modify(new_index, adjust_offset);
456                modify(new_len, adjust_len);
457            }
458        }
459    }
460}
461
462#[cfg(feature = "text")]
463mod text_additions {
464    use super::*;
465    use crate::text::DiffableStr;
466    use std::borrow::Cow;
467
468    /// The text interface can produce changes over [`DiffableStr`] implementing
469    /// values.  As those are generic interfaces for different types of strings
470    /// utility methods to make working with standard rust strings more enjoyable.
471    impl<'s, T: DiffableStr + ?Sized> Change<&'s T> {
472        /// Returns the value as string if it is utf-8.
473        pub fn as_str(&self) -> Option<&'s str> {
474            T::as_str(self.value)
475        }
476
477        /// Returns the value (lossy) decoded as utf-8 string.
478        pub fn to_string_lossy(&self) -> Cow<'s, str> {
479            T::to_string_lossy(self.value)
480        }
481
482        /// Returns `true` if this change does not end in a newline and must be
483        /// followed up by one if line based diffs are used.
484        ///
485        /// The [`std::fmt::Display`] implementation of [`Change`] will automatically
486        /// insert a newline after the value if this is true.
487        pub fn missing_newline(&self) -> bool {
488            !T::ends_with_newline(self.value)
489        }
490    }
491
492    impl<T: DiffableStr + ?Sized> fmt::Display for Change<&T> {
493        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
494            write!(
495                f,
496                "{}{}",
497                self.to_string_lossy(),
498                if self.missing_newline() { "\n" } else { "" }
499            )
500        }
501    }
502}