present/
diff.rs

1use crate::{common::*, RopeExt};
2
3/// Represents a diff in a [`File`](crate::File)
4#[derive(Debug, Clone)]
5pub struct Diff {
6  /// A string that will be inserted into `range`
7  pub content: String,
8  /// A range from the original string that will get replaced
9  pub range: Range<usize>,
10}
11
12impl Diff {
13  /// Adjusts the diff's range by the given offset.
14  ///
15  /// This method modifies the start and end points of the diff's range
16  /// based on the provided offset. It handles both positive and negative
17  /// offsets, using saturating arithmetic to prevent underflow or overflow.
18  pub(crate) fn offset(&mut self, offset: isize) {
19    if offset >= 0 {
20      let offset = offset as usize;
21      self.range.start = self.range.start.saturating_add(offset);
22      self.range.end = self.range.end.saturating_add(offset);
23    } else {
24      let abs_offset = offset.unsigned_abs();
25      self.range.start = self.range.start.saturating_sub(abs_offset);
26      self.range.end = self.range.end.saturating_sub(abs_offset);
27    }
28  }
29
30  /// Prints the diff by using [`TextDiff`].
31  ///
32  /// Since the struct does not store any context of what it's diffing on, you
33  /// need to supply the original content (as a [`Rope`] reference) to this
34  /// function.
35  pub fn print(&self, content: &Rope) {
36    for change in TextDiff::from_lines(
37      &content.to_string(),
38      &content.simulate(self.clone()).to_string(),
39    )
40    .iter_all_changes()
41    {
42      let (sign, style) = match change.tag() {
43        ChangeTag::Delete => ("-", Style::new().red()),
44        ChangeTag::Insert => ("+", Style::new().green()),
45        ChangeTag::Equal => ("", Style::new()),
46      };
47      eprint!("{}{}", style.apply_to(sign).bold(), style.apply_to(change));
48    }
49  }
50}
51
52#[cfg(test)]
53mod tests {
54  use super::*;
55
56  fn diff() -> Diff {
57    Diff {
58      content: "foobar".into(),
59      range: 1..4,
60    }
61  }
62
63  #[test]
64  fn offset_positive() {
65    let mut diff = diff();
66    diff.offset(1);
67    assert_eq!(diff.range, 2..5);
68  }
69
70  #[test]
71  fn offset_negative() {
72    let mut diff = diff();
73    diff.offset(-1);
74    assert_eq!(diff.range, 0..3);
75  }
76
77  #[test]
78  fn offset_negative_overflow() {
79    let mut diff = diff();
80    diff.offset(-10);
81    assert_eq!(diff.range, 0..0);
82  }
83
84  #[test]
85  fn offset_positive_large() {
86    let mut diff = diff();
87
88    diff.offset(isize::MAX);
89
90    assert_eq!(
91      diff.range,
92      (1 + isize::MAX as usize)..(4 + isize::MAX as usize)
93    );
94  }
95
96  #[test]
97  fn offset_negative_large() {
98    let mut diff = diff();
99    diff.offset(isize::MIN);
100    assert_eq!(diff.range, 0..0);
101  }
102}