Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-vcs/src/diff.rs')
-rw-r--r--helix-vcs/src/diff.rs124
1 files changed, 50 insertions, 74 deletions
diff --git a/helix-vcs/src/diff.rs b/helix-vcs/src/diff.rs
index d87f3ce7..c72deb7e 100644
--- a/helix-vcs/src/diff.rs
+++ b/helix-vcs/src/diff.rs
@@ -1,18 +1,16 @@
-use std::iter::Peekable;
+use std::ops::Range;
use std::sync::Arc;
use helix_core::Rope;
use helix_event::RenderLockGuard;
use imara_diff::Algorithm;
-use parking_lot::{RwLock, RwLockReadGuard};
+use parking_lot::{Mutex, MutexGuard};
use tokio::sync::mpsc::{unbounded_channel, UnboundedSender};
use tokio::task::JoinHandle;
use tokio::time::Instant;
use crate::diff::worker::DiffWorker;
-pub use imara_diff::Hunk;
-
mod line_cache;
mod worker;
@@ -35,11 +33,10 @@ struct DiffInner {
hunks: Vec<Hunk>,
}
-/// Representation of a diff that can be updated.
#[derive(Clone, Debug)]
pub struct DiffHandle {
channel: UnboundedSender<Event>,
- diff: Arc<RwLock<DiffInner>>,
+ diff: Arc<Mutex<DiffInner>>,
inverted: bool,
}
@@ -50,12 +47,12 @@ impl DiffHandle {
fn new_with_handle(diff_base: Rope, doc: Rope) -> (DiffHandle, JoinHandle<()>) {
let (sender, receiver) = unbounded_channel();
- let diff: Arc<RwLock<DiffInner>> = Arc::default();
+ let diff: Arc<Mutex<DiffInner>> = Arc::default();
let worker = DiffWorker {
channel: receiver,
diff: diff.clone(),
+ new_hunks: Vec::default(),
diff_finished_notify: Arc::default(),
- diff_alloc: imara_diff::Diff::default(),
};
let handle = tokio::spawn(worker.run(diff_base, doc));
let differ = DiffHandle {
@@ -66,15 +63,13 @@ impl DiffHandle {
(differ, handle)
}
- /// Switch base and modified texts' roles
pub fn invert(&mut self) {
self.inverted = !self.inverted;
}
- /// Load the actual diff
- pub fn load(&self) -> Diff<'_> {
+ pub fn load(&self) -> Diff {
Diff {
- diff: self.diff.read(),
+ diff: self.diff.lock(),
inverted: self.inverted,
}
}
@@ -92,7 +87,6 @@ impl DiffHandle {
self.update_document_impl(doc, self.inverted, Some(RenderLock { lock, timeout }))
}
- /// Updates the base text of the diff. Returns if the update was successful.
pub fn update_diff_base(&self, diff_base: Rope) -> bool {
self.update_document_impl(diff_base, !self.inverted, None)
}
@@ -123,16 +117,57 @@ const MAX_DIFF_LINES: usize = 64 * u16::MAX as usize;
// cap average line length to 128 for files with MAX_DIFF_LINES
const MAX_DIFF_BYTES: usize = MAX_DIFF_LINES * 128;
+/// A single change in a file potentially spanning multiple lines
+/// Hunks produced by the differs are always ordered by their position
+/// in the file and non-overlapping.
+/// Specifically for any two hunks `x` and `y` the following properties hold:
+///
+/// ``` no_compile
+/// assert!(x.before.end <= y.before.start);
+/// assert!(x.after.end <= y.after.start);
+/// ```
+#[derive(PartialEq, Eq, Clone, Debug)]
+pub struct Hunk {
+ pub before: Range<u32>,
+ pub after: Range<u32>,
+}
+
+impl Hunk {
+ /// Can be used instead of `Option::None` for better performance
+ /// because lines larger then `i32::MAX` are not supported by `imara-diff` anyways.
+ /// Has some nice properties where it usually is not necessary to check for `None` separately:
+ /// Empty ranges fail contains checks and also fails smaller then checks.
+ pub const NONE: Hunk = Hunk {
+ before: u32::MAX..u32::MAX,
+ after: u32::MAX..u32::MAX,
+ };
+
+ /// Inverts a change so that `before`
+ 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()
+ }
+}
+
/// A list of changes in a file sorted in ascending
/// non-overlapping order
#[derive(Debug)]
pub struct Diff<'a> {
- diff: RwLockReadGuard<'a, DiffInner>,
+ diff: MutexGuard<'a, DiffInner>,
inverted: bool,
}
impl Diff<'_> {
- /// Returns the base [Rope] of the [Diff]
pub fn diff_base(&self) -> &Rope {
if self.inverted {
&self.diff.doc
@@ -141,7 +176,6 @@ impl Diff<'_> {
}
}
- /// Returns the [Rope] being compared against
pub fn doc(&self) -> &Rope {
if self.inverted {
&self.diff.diff_base
@@ -172,7 +206,6 @@ impl Diff<'_> {
self.len() == 0
}
- /// Gives the index of the first hunk after the given line, if one exists.
pub fn next_hunk(&self, line: u32) -> Option<u32> {
let hunk_range = if self.inverted {
|hunk: &Hunk| hunk.before.clone()
@@ -199,7 +232,6 @@ impl Diff<'_> {
}
}
- /// Gives the index of the first hunk before the given line, if one exists.
pub fn prev_hunk(&self, line: u32) -> Option<u32> {
let hunk_range = if self.inverted {
|hunk: &Hunk| hunk.before.clone()
@@ -227,23 +259,6 @@ impl Diff<'_> {
}
}
- /// Iterates over all hunks that intersect with the given line ranges.
- ///
- /// Hunks are returned at most once even when intersecting with multiple of the line
- /// ranges.
- pub fn hunks_intersecting_line_ranges<I>(&self, line_ranges: I) -> impl Iterator<Item = &Hunk>
- where
- I: Iterator<Item = (usize, usize)>,
- {
- HunksInLineRangesIter {
- hunks: &self.diff.hunks,
- line_ranges: line_ranges.peekable(),
- inverted: self.inverted,
- cursor: 0,
- }
- }
-
- /// Returns the index of the hunk containing the given line if it exists.
pub fn hunk_at(&self, line: u32, include_removal: bool) -> Option<u32> {
let hunk_range = if self.inverted {
|hunk: &Hunk| hunk.before.clone()
@@ -275,42 +290,3 @@ impl Diff<'_> {
}
}
}
-
-pub struct HunksInLineRangesIter<'a, I: Iterator<Item = (usize, usize)>> {
- hunks: &'a [Hunk],
- line_ranges: Peekable<I>,
- inverted: bool,
- cursor: usize,
-}
-
-impl<'a, I: Iterator<Item = (usize, usize)>> Iterator for HunksInLineRangesIter<'a, I> {
- type Item = &'a Hunk;
-
- fn next(&mut self) -> Option<Self::Item> {
- let hunk_range = if self.inverted {
- |hunk: &Hunk| hunk.before.clone()
- } else {
- |hunk: &Hunk| hunk.after.clone()
- };
-
- loop {
- let (start_line, end_line) = self.line_ranges.peek()?;
- let hunk = self.hunks.get(self.cursor)?;
-
- if (hunk_range(hunk).end as usize) < *start_line {
- // If the hunk under the cursor comes before this range, jump the cursor
- // ahead to the next hunk that overlaps with the line range.
- self.cursor += self.hunks[self.cursor..]
- .partition_point(|hunk| (hunk_range(hunk).end as usize) < *start_line);
- } else if (hunk_range(hunk).start as usize) <= *end_line {
- // If the hunk under the cursor overlaps with this line range, emit it
- // and move the cursor up so that the hunk cannot be emitted twice.
- self.cursor += 1;
- return Some(hunk);
- } else {
- // Otherwise, go to the next line range.
- self.line_ranges.next();
- }
- }
- }
-}