use futures::{channel::oneshot, future::OptionFuture};use git2::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as GitPatch};use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter};use language::{Language, LanguageRegistry};use rope::Rope;use std::{cmp, future::Future, iter, ops::Range, sync::Arc};use sum_tree::SumTree;use text::ToOffset as _;use text::{Anchor, Bias, BufferId, OffsetRangeExt, Point};use util::ResultExt; pub struct BufferDiff { pub buffer_id: BufferId, inner: BufferDiffInner, secondary_diff: Option<Entity<BufferDiff>>,} #[derive(Clone, Debug)]pub struct BufferDiffSnapshot { inner: BufferDiffInner, secondary_diff: Option<Box<BufferDiffSnapshot>>, pub is_single_insertion: bool,} #[derive(Clone)]struct BufferDiffInner { hunks: SumTree<InternalDiffHunk>, base_text: Option<language::BufferSnapshot>,} #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]pub enum DiffHunkStatus { Added(DiffHunkSecondaryStatus), Modified(DiffHunkSecondaryStatus), Removed(DiffHunkSecondaryStatus),} #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]pub enum DiffHunkSecondaryStatus { HasSecondaryHunk, OverlapsWithSecondaryHunk, None,} /// A diff hunk resolved to rows in the buffer.#[derive(Debug, Clone, PartialEq, Eq)]pub struct DiffHunk { /// The buffer range, expressed in terms of rows. pub row_range: Range<u32>, /// The range in the buffer to which this hunk corresponds. pub buffer_range: Range<Anchor>, /// The range in the buffer's diff base text to which this hunk corresponds. pub diff_base_byte_range: Range<usize>, pub secondary_status: DiffHunkSecondaryStatus, pub secondary_diff_base_byte_range: Option<Range<usize>>,} /// We store [`InternalDiffHunk`]s internally so we don't need to store the additional row range.#[derive(Debug, Clone, PartialEq, Eq)]struct InternalDiffHunk { buffer_range: Range<Anchor>, diff_base_byte_range: Range<usize>,} impl sum_tree::Item for InternalDiffHunk { type Summary = DiffHunkSummary; fn summary(&self, _cx: &text::BufferSnapshot) -> Self::Summary { DiffHunkSummary { buffer_range: self.buffer_range.clone(), } }} #[derive(Debug, Default, Clone)]pub struct DiffHunkSummary { buffer_range: Range<Anchor>,} impl sum_tree::Summary for DiffHunkSummary { type Context = text::BufferSnapshot; fn zero(_cx: &Self::Context) -> Self { Default::default() } fn add_summary(&mut self, other: &Self, buffer: &Self::Context) { self.buffer_range.start = self .buffer_range .start .min(&other.buffer_range.start, buffer); self.buffer_range.end = self.buffer_range.end.max(&other.buffer_range.end, buffer); }} impl<'a> sum_tree::SeekTarget<'a, DiffHunkSummary, DiffHunkSummary> for Anchor { fn cmp( &self, cursor_location: &DiffHunkSummary, buffer: &text::BufferSnapshot, ) -> cmp::Ordering { self.cmp(&cursor_location.buffer_range.end, buffer) }} impl std::fmt::Debug for BufferDiffInner { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("BufferDiffSnapshot") .field("hunks", &self.hunks) .finish() }} impl BufferDiffSnapshot { pub fn is_empty(&self) -> bool { self.inner.hunks.is_empty() } pub fn secondary_diff(&self) -> Option<&BufferDiffSnapshot> { self.secondary_diff.as_deref() } pub fn hunks_intersecting_range<'a>( &'a self, range: Range<Anchor>, buffer: &'a text::BufferSnapshot, ) -> impl 'a + Iterator<Item = DiffHunk> { let unstaged_counterpart = self.secondary_diff.as_ref().map(|diff| &diff.inner); self.inner .hunks_intersecting_range(range, buffer, unstaged_counterpart) } pub fn hunks_intersecting_range_rev<'a>( &'a self, range: Range<Anchor>, buffer: &'a text::BufferSnapshot, ) -> impl 'a + Iterator<Item = DiffHunk> { self.inner.hunks_intersecting_range_rev(range, buffer) } pub fn base_text(&self) -> Option<&language::BufferSnapshot> { self.inner.base_text.as_ref() } pub fn base_texts_eq(&self, other: &Self) -> bool { match (other.base_text(), self.base_text()) { (None, None) => true, (None, Some(_)) => false, (Some(_), None) => false, (Some(old), Some(new)) => { let (old_id, old_empty) = (old.remote_id(), old.is_empty()); let (new_id, new_empty) = (new.remote_id(), new.is_empty()); new_id == old_id || (new_empty && old_empty) } } } fn buffer_range_to_unchanged_diff_base_range( &self, buffer_range: Range<Anchor>, buffer: &text::BufferSnapshot, ) -> Option<Range<usize>> { let mut hunks = self.inner.hunks.iter(); let mut start = 0; let mut pos = buffer.anchor_before(0); while let Some(hunk) = hunks.next() { assert!(buffer_range.start.cmp(&pos, buffer).is_ge()); assert!(hunk.buffer_range.start.cmp(&pos, buffer).is_ge()); if hunk .buffer_range .start .cmp(&buffer_range.end, buffer) .is_ge() { // target buffer range is contained in the unchanged stretch leading up to this next hunk, // so do a final adjustment based on that break; } // if the target buffer range intersects this hunk at all, no dice if buffer_range .start .cmp(&hunk.buffer_range.end, buffer) .is_lt() { return None; } start += hunk.buffer_range.start.to_offset(buffer) - pos.to_offset(buffer); start += hunk.diff_base_byte_range.end - hunk.diff_base_byte_range.start; pos = hunk.buffer_range.end; } start += buffer_range.start.to_offset(buffer) - pos.to_offset(buffer); let end = start + buffer_range.end.to_offset(buffer) - buffer_range.start.to_offset(buffer); Some(start..end) } pub fn secondary_edits_for_stage_or_unstage( &self, stage: bool, hunks: impl Iterator<Item = (Range<usize>, Option<Range<usize>>, Range<Anchor>)>, buffer: &text::BufferSnapshot, ) -> Vec<(Range<usize>, String)> { let Some(secondary_diff) = self.secondary_diff() else { log::debug!("no secondary diff"); return Vec::new(); }; let index_base = secondary_diff.base_text().map_or_else( || Rope::from(""), |snapshot| snapshot.text.as_rope().clone(), ); let head_base = self.base_text().map_or_else( || Rope::from(""), |snapshot| snapshot.text.as_rope().clone(), ); log::debug!("original: {:?}", index_base.to_string()); let mut edits = Vec::new(); for (diff_base_byte_range, secondary_diff_base_byte_range, buffer_range) in hunks { let (index_byte_range, replacement_text) = if stage { log::debug!("staging"); let mut replacement_text = String::new(); let Some(index_byte_range) = secondary_diff_base_byte_range.clone() else { log::debug!("not a stageable hunk"); continue; }; log::debug!("using {:?}", index_byte_range); for chunk in buffer.text_for_range(buffer_range.clone()) { replacement_text.push_str(chunk); } (index_byte_range, replacement_text) } else { log::debug!("unstaging"); let mut replacement_text = String::new(); let Some(index_byte_range) = secondary_diff .buffer_range_to_unchanged_diff_base_range(buffer_range.clone(), &buffer) else { log::debug!("not an unstageable hunk"); continue; }; for chunk in head_base.chunks_in_range(diff_base_byte_range.clone()) { replacement_text.push_str(chunk); } (index_byte_range, replacement_text) }; edits.push((index_byte_range, replacement_text)); } log::debug!("edits: {edits:?}"); edits }} impl BufferDiffInner { fn hunks_intersecting_range<'a>( &'a self, range: Range<Anchor>, buffer: &'a text::BufferSnapshot, secondary: Option<&'a Self>, ) -> impl 'a + Iterator<Item = DiffHunk> { let range = range.to_offset(buffer); let mut cursor = self .hunks .filter::<_, DiffHunkSummary>(buffer, move |summary| { let summary_range = summary.buffer_range.to_offset(buffer); let before_start = summary_range.end < range.start; let after_end = summary_range.start > range.end; !before_start && !after_end }); let anchor_iter = iter::from_fn(move || { cursor.next(buffer); cursor.item() }) .flat_map(move |hunk| { [ ( &hunk.buffer_range.start, (hunk.buffer_range.start, hunk.diff_base_byte_range.start), ), ( &hunk.buffer_range.end, (hunk.buffer_range.end, hunk.diff_base_byte_range.end), ), ] }); let mut secondary_cursor = secondary.as_ref().map(|diff| { let mut cursor = diff.hunks.cursor::<DiffHunkSummary>(buffer); cursor.next(buffer); cursor }); let mut summaries = buffer.summaries_for_anchors_with_payload::<Point, _, _>(anchor_iter); iter::from_fn(move || loop { let (start_point, (start_anchor, start_base)) = summaries.next()?; let (mut end_point, (mut end_anchor, end_base)) = summaries.next()?; if !start_anchor.is_valid(buffer) { continue; } if end_point.column > 0 { end_point.row += 1; end_point.column = 0; end_anchor = buffer.anchor_before(end_point); } let mut secondary_status = DiffHunkSecondaryStatus::None; let mut secondary_diff_base_byte_range = None; if let Some(secondary_cursor) = secondary_cursor.as_mut() { if start_anchor .cmp(&secondary_cursor.start().buffer_range.start, buffer) .is_gt() { secondary_cursor.seek_forward(&end_anchor, Bias::Left, buffer); } if let Some(secondary_hunk) = secondary_cursor.item() { let mut secondary_range = secondary_hunk.buffer_range.to_point(buffer); if secondary_range.end.column > 0 { secondary_range.end.row += 1; secondary_range.end.column = 0; } if secondary_range == (start_point..end_point) { secondary_status = DiffHunkSecondaryStatus::HasSecondaryHunk; secondary_diff_base_byte_range = Some(secondary_hunk.diff_base_byte_range.clone()); } else if secondary_range.start <= end_point { secondary_status = DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk; } } } return Some(DiffHunk { row_range: start_point.row..end_point.row, diff_base_byte_range: start_base..end_base, buffer_range: start_anchor..end_anchor, secondary_status, secondary_diff_base_byte_range, }); }) } fn hunks_intersecting_range_rev<'a>( &'a self, range: Range<Anchor>, buffer: &'a text::BufferSnapshot, ) -> impl 'a + Iterator<Item = DiffHunk> { let mut cursor = self .hunks .filter::<_, DiffHunkSummary>(buffer, move |summary| { let before_start = summary.buffer_range.end.cmp(&range.start, buffer).is_lt(); let after_end = summary.buffer_range.start.cmp(&range.end, buffer).is_gt(); !before_start && !after_end }); iter::from_fn(move || { cursor.prev(buffer); let hunk = cursor.item()?; let range = hunk.buffer_range.to_point(buffer); let end_row = if range.end.column > 0 { range.end.row + 1 } else { range.end.row }; Some(DiffHunk { row_range: range.start.row..end_row, diff_base_byte_range: hunk.diff_base_byte_range.clone(), buffer_range: hunk.buffer_range.clone(), // The secondary status is not used by callers of this method. secondary_status: DiffHunkSecondaryStatus::None, secondary_diff_base_byte_range: None, }) }) } fn compare(&self, old: &Self, new_snapshot: &text::BufferSnapshot) -> Option<Range<Anchor>> { let mut new_cursor = self.hunks.cursor::<()>(new_snapshot); let mut old_cursor = old.hunks.cursor::<()>(new_snapshot); old_cursor.next(new_snapshot); new_cursor.next(new_snapshot); let mut start = None; let mut end = None; loop { match (new_cursor.item(), old_cursor.item()) { (Some(new_hunk), Some(old_hunk)) => { match new_hunk .buffer_range .start .cmp(&old_hunk.buffer_range.start, new_snapshot) { cmp::Ordering::Less => { start.get_or_insert(new_hunk.buffer_range.start); end.replace(new_hunk.buffer_range.end); new_cursor.next(new_snapshot); } cmp::Ordering::Equal => { if new_hunk != old_hunk { start.get_or_insert(new_hunk.buffer_range.start); if old_hunk .buffer_range .end .cmp(&new_hunk.buffer_range.end, new_snapshot) .is_ge() { end.replace(old_hunk.buffer_range.end); } else { end.replace(new_hunk.buffer_range.end); } } new_cursor.next(new_snapshot); old_cursor.next(new_snapshot); } cmp::Ordering::Greater => { start.get_or_insert(old_hunk.buffer_range.start); end.replace(old_hunk.buffer_range.end); old_cursor.next(new_snapshot); } } } (Some(new_hunk), None) => { start.get_or_insert(new_hunk.buffer_range.start); end.replace(new_hunk.buffer_range.end); new_cursor.next(new_snapshot); } (None, Some(old_hunk)) => { start.get_or_insert(old_hunk.buffer_range.start); end.replace(old_hunk.buffer_range.end); old_cursor.next(new_snapshot); } (None, None) => break, } } start.zip(end).map(|(start, end)| start..end) }} fn compute_hunks( diff_base: Option<(Arc<String>, Rope)>, buffer: text::BufferSnapshot,) -> SumTree<InternalDiffHunk> { let mut tree = SumTree::new(&buffer); if let Some((diff_base, diff_base_rope)) = diff_base { let buffer_text = buffer.as_rope().to_string(); let mut options = GitOptions::default(); options.context_lines(0); let patch = GitPatch::from_buffers( diff_base.as_bytes(), None, buffer_text.as_bytes(), None, Some(&mut options), ) .log_err(); // A common case in Zed is that the empty buffer is represented as just a newline, // but if we just compute a naive diff you get a "preserved" line in the middle, // which is a bit odd. if buffer_text == "\n" && diff_base.ends_with("\n") && diff_base.len() > 1 { tree.push( InternalDiffHunk { buffer_range: buffer.anchor_before(0)..buffer.anchor_before(0), diff_base_byte_range: 0..diff_base.len() - 1, }, &buffer, ); return tree; } if let Some(patch) = patch { let mut divergence = 0; for hunk_index in 0..patch.num_hunks() { let hunk = process_patch_hunk( &patch, hunk_index, &diff_base_rope, &buffer, &mut divergence, ); tree.push(hunk, &buffer); } } } tree} fn process_patch_hunk( patch: &GitPatch<'_>, hunk_index: usize, diff_base: &Rope, buffer: &text::BufferSnapshot, buffer_row_divergence: &mut i64,Lines 1–500 of 1517. Load more
use futures::{channel::oneshot, future::OptionFuture}; use git2::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as GitPatch}; use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter}; use language::{Language, LanguageRegistry}; use rope::Rope; use std::{cmp, future::Future, iter, ops::Range, sync::Arc}; use sum_tree::SumTree; use text::ToOffset as _; use text::{Anchor, Bias, BufferId, OffsetRangeExt, Point}; use util::ResultExt; pub struct BufferDiff { pub buffer_id: BufferId, inner: BufferDiffInner, secondary_diff: Option<Entity<BufferDiff>>, } #[derive(Clone, Debug)] pub struct BufferDiffSnapshot { inner: BufferDiffInner, secondary_diff: Option<Box<BufferDiffSnapshot>>, pub is_single_insertion: bool, } #[derive(Clone)] struct BufferDiffInner { hunks: SumTree<InternalDiffHunk>, base_text: Option<language::BufferSnapshot>, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum DiffHunkStatus { Added(DiffHunkSecondaryStatus), Modified(DiffHunkSecondaryStatus), Removed(DiffHunkSecondaryStatus), } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum DiffHunkSecondaryStatus { HasSecondaryHunk, OverlapsWithSecondaryHunk, None, } /// A diff hunk resolved to rows in the buffer. #[derive(Debug, Clone, PartialEq, Eq)] pub struct DiffHunk { /// The buffer range, expressed in terms of rows. pub row_range: Range<u32>, /// The range in the buffer to which this hunk corresponds. pub buffer_range: Range<Anchor>, /// The range in the buffer's diff base text to which this hunk corresponds. pub diff_base_byte_range: Range<usize>, pub secondary_status: DiffHunkSecondaryStatus, pub secondary_diff_base_byte_range: Option<Range<usize>>, } /// We store [`InternalDiffHunk`]s internally so we don't need to store the additional row range. #[derive(Debug, Clone, PartialEq, Eq)] struct InternalDiffHunk { buffer_range: Range<Anchor>, diff_base_byte_range: Range<usize>, } impl sum_tree::Item for InternalDiffHunk { type Summary = DiffHunkSummary; fn summary(&self, _cx: &text::BufferSnapshot) -> Self::Summary { DiffHunkSummary { buffer_range: self.buffer_range.clone(), } } } #[derive(Debug, Default, Clone)] pub struct DiffHunkSummary { buffer_range: Range<Anchor>, } impl sum_tree::Summary for DiffHunkSummary { type Context = text::BufferSnapshot; fn zero(_cx: &Self::Context) -> Self { Default::default() } fn add_summary(&mut self, other: &Self, buffer: &Self::Context) { self.buffer_range.start = self .buffer_range .start .min(&other.buffer_range.start, buffer); self.buffer_range.end = self.buffer_range.end.max(&other.buffer_range.end, buffer); } } impl<'a> sum_tree::SeekTarget<'a, DiffHunkSummary, DiffHunkSummary> for Anchor { fn cmp( &self, cursor_location: &DiffHunkSummary, buffer: &text::BufferSnapshot, ) -> cmp::Ordering { self.cmp(&cursor_location.buffer_range.end, buffer) } } impl std::fmt::Debug for BufferDiffInner { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("BufferDiffSnapshot") .field("hunks", &self.hunks) .finish() } } impl BufferDiffSnapshot { pub fn is_empty(&self) -> bool { self.inner.hunks.is_empty() } pub fn secondary_diff(&self) -> Option<&BufferDiffSnapshot> { self.secondary_diff.as_deref() } pub fn hunks_intersecting_range<'a>( &'a self, range: Range<Anchor>, buffer: &'a text::BufferSnapshot, ) -> impl 'a + Iterator<Item = DiffHunk> { let unstaged_counterpart = self.secondary_diff.as_ref().map(|diff| &diff.inner); self.inner .hunks_intersecting_range(range, buffer, unstaged_counterpart) } pub fn hunks_intersecting_range_rev<'a>( &'a self, range: Range<Anchor>, buffer: &'a text::BufferSnapshot, ) -> impl 'a + Iterator<Item = DiffHunk> { self.inner.hunks_intersecting_range_rev(range, buffer) } pub fn base_text(&self) -> Option<&language::BufferSnapshot> { self.inner.base_text.as_ref() } pub fn base_texts_eq(&self, other: &Self) -> bool { match (other.base_text(), self.base_text()) { (None, None) => true, (None, Some(_)) => false, (Some(_), None) => false, (Some(old), Some(new)) => { let (old_id, old_empty) = (old.remote_id(), old.is_empty()); let (new_id, new_empty) = (new.remote_id(), new.is_empty()); new_id == old_id || (new_empty && old_empty) } } } fn buffer_range_to_unchanged_diff_base_range( &self, buffer_range: Range<Anchor>, buffer: &text::BufferSnapshot, ) -> Option<Range<usize>> { let mut hunks = self.inner.hunks.iter(); let mut start = 0; let mut pos = buffer.anchor_before(0); while let Some(hunk) = hunks.next() { assert!(buffer_range.start.cmp(&pos, buffer).is_ge()); assert!(hunk.buffer_range.start.cmp(&pos, buffer).is_ge()); if hunk .buffer_range .start .cmp(&buffer_range.end, buffer) .is_ge() { // target buffer range is contained in the unchanged stretch leading up to this next hunk, // so do a final adjustment based on that break; } // if the target buffer range intersects this hunk at all, no dice if buffer_range .start .cmp(&hunk.buffer_range.end, buffer) .is_lt() { return None; } start += hunk.buffer_range.start.to_offset(buffer) - pos.to_offset(buffer); start += hunk.diff_base_byte_range.end - hunk.diff_base_byte_range.start; pos = hunk.buffer_range.end; } start += buffer_range.start.to_offset(buffer) - pos.to_offset(buffer); let end = start + buffer_range.end.to_offset(buffer) - buffer_range.start.to_offset(buffer); Some(start..end) } pub fn secondary_edits_for_stage_or_unstage( &self, stage: bool, hunks: impl Iterator<Item = (Range<usize>, Option<Range<usize>>, Range<Anchor>)>, buffer: &text::BufferSnapshot, ) -> Vec<(Range<usize>, String)> { let Some(secondary_diff) = self.secondary_diff() else { log::debug!("no secondary diff"); return Vec::new(); }; let index_base = secondary_diff.base_text().map_or_else( || Rope::from(""), |snapshot| snapshot.text.as_rope().clone(), ); let head_base = self.base_text().map_or_else( || Rope::from(""), |snapshot| snapshot.text.as_rope().clone(), ); log::debug!("original: {:?}", index_base.to_string()); let mut edits = Vec::new(); for (diff_base_byte_range, secondary_diff_base_byte_range, buffer_range) in hunks { let (index_byte_range, replacement_text) = if stage { log::debug!("staging"); let mut replacement_text = String::new(); let Some(index_byte_range) = secondary_diff_base_byte_range.clone() else { log::debug!("not a stageable hunk"); continue; }; log::debug!("using {:?}", index_byte_range); for chunk in buffer.text_for_range(buffer_range.clone()) { replacement_text.push_str(chunk); } (index_byte_range, replacement_text) } else { log::debug!("unstaging"); let mut replacement_text = String::new(); let Some(index_byte_range) = secondary_diff .buffer_range_to_unchanged_diff_base_range(buffer_range.clone(), &buffer) else { log::debug!("not an unstageable hunk"); continue; }; for chunk in head_base.chunks_in_range(diff_base_byte_range.clone()) { replacement_text.push_str(chunk); } (index_byte_range, replacement_text) }; edits.push((index_byte_range, replacement_text)); } log::debug!("edits: {edits:?}"); edits } } impl BufferDiffInner { fn hunks_intersecting_range<'a>( &'a self, range: Range<Anchor>, buffer: &'a text::BufferSnapshot, secondary: Option<&'a Self>, ) -> impl 'a + Iterator<Item = DiffHunk> { let range = range.to_offset(buffer); let mut cursor = self .hunks .filter::<_, DiffHunkSummary>(buffer, move |summary| { let summary_range = summary.buffer_range.to_offset(buffer); let before_start = summary_range.end < range.start; let after_end = summary_range.start > range.end; !before_start && !after_end }); let anchor_iter = iter::from_fn(move || { cursor.next(buffer); cursor.item() }) .flat_map(move |hunk| { [ ( &hunk.buffer_range.start, (hunk.buffer_range.start, hunk.diff_base_byte_range.start), ), ( &hunk.buffer_range.end, (hunk.buffer_range.end, hunk.diff_base_byte_range.end), ), ] }); let mut secondary_cursor = secondary.as_ref().map(|diff| { let mut cursor = diff.hunks.cursor::<DiffHunkSummary>(buffer); cursor.next(buffer); cursor }); let mut summaries = buffer.summaries_for_anchors_with_payload::<Point, _, _>(anchor_iter); iter::from_fn(move || loop { let (start_point, (start_anchor, start_base)) = summaries.next()?; let (mut end_point, (mut end_anchor, end_base)) = summaries.next()?; if !start_anchor.is_valid(buffer) { continue; } if end_point.column > 0 { end_point.row += 1; end_point.column = 0; end_anchor = buffer.anchor_before(end_point); } let mut secondary_status = DiffHunkSecondaryStatus::None; let mut secondary_diff_base_byte_range = None; if let Some(secondary_cursor) = secondary_cursor.as_mut() { if start_anchor .cmp(&secondary_cursor.start().buffer_range.start, buffer) .is_gt() { secondary_cursor.seek_forward(&end_anchor, Bias::Left, buffer); } if let Some(secondary_hunk) = secondary_cursor.item() { let mut secondary_range = secondary_hunk.buffer_range.to_point(buffer); if secondary_range.end.column > 0 { secondary_range.end.row += 1; secondary_range.end.column = 0; } if secondary_range == (start_point..end_point) { secondary_status = DiffHunkSecondaryStatus::HasSecondaryHunk; secondary_diff_base_byte_range = Some(secondary_hunk.diff_base_byte_range.clone()); } else if secondary_range.start <= end_point { secondary_status = DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk; } } } return Some(DiffHunk { row_range: start_point.row..end_point.row, diff_base_byte_range: start_base..end_base, buffer_range: start_anchor..end_anchor, secondary_status, secondary_diff_base_byte_range, }); }) } fn hunks_intersecting_range_rev<'a>( &'a self, range: Range<Anchor>, buffer: &'a text::BufferSnapshot, ) -> impl 'a + Iterator<Item = DiffHunk> { let mut cursor = self .hunks .filter::<_, DiffHunkSummary>(buffer, move |summary| { let before_start = summary.buffer_range.end.cmp(&range.start, buffer).is_lt(); let after_end = summary.buffer_range.start.cmp(&range.end, buffer).is_gt(); !before_start && !after_end }); iter::from_fn(move || { cursor.prev(buffer); let hunk = cursor.item()?; let range = hunk.buffer_range.to_point(buffer); let end_row = if range.end.column > 0 { range.end.row + 1 } else { range.end.row }; Some(DiffHunk { row_range: range.start.row..end_row, diff_base_byte_range: hunk.diff_base_byte_range.clone(), buffer_range: hunk.buffer_range.clone(), // The secondary status is not used by callers of this method. secondary_status: DiffHunkSecondaryStatus::None, secondary_diff_base_byte_range: None, }) }) } fn compare(&self, old: &Self, new_snapshot: &text::BufferSnapshot) -> Option<Range<Anchor>> { let mut new_cursor = self.hunks.cursor::<()>(new_snapshot); let mut old_cursor = old.hunks.cursor::<()>(new_snapshot); old_cursor.next(new_snapshot); new_cursor.next(new_snapshot); let mut start = None; let mut end = None; loop { match (new_cursor.item(), old_cursor.item()) { (Some(new_hunk), Some(old_hunk)) => { match new_hunk .buffer_range .start .cmp(&old_hunk.buffer_range.start, new_snapshot) { cmp::Ordering::Less => { start.get_or_insert(new_hunk.buffer_range.start); end.replace(new_hunk.buffer_range.end); new_cursor.next(new_snapshot); } cmp::Ordering::Equal => { if new_hunk != old_hunk { start.get_or_insert(new_hunk.buffer_range.start); if old_hunk .buffer_range .end .cmp(&new_hunk.buffer_range.end, new_snapshot) .is_ge() { end.replace(old_hunk.buffer_range.end); } else { end.replace(new_hunk.buffer_range.end); } } new_cursor.next(new_snapshot); old_cursor.next(new_snapshot); } cmp::Ordering::Greater => { start.get_or_insert(old_hunk.buffer_range.start); end.replace(old_hunk.buffer_range.end); old_cursor.next(new_snapshot); } } } (Some(new_hunk), None) => { start.get_or_insert(new_hunk.buffer_range.start); end.replace(new_hunk.buffer_range.end); new_cursor.next(new_snapshot); } (None, Some(old_hunk)) => { start.get_or_insert(old_hunk.buffer_range.start); end.replace(old_hunk.buffer_range.end); old_cursor.next(new_snapshot); } (None, None) => break, } } start.zip(end).map(|(start, end)| start..end) } } fn compute_hunks( diff_base: Option<(Arc<String>, Rope)>, buffer: text::BufferSnapshot, ) -> SumTree<InternalDiffHunk> { let mut tree = SumTree::new(&buffer); if let Some((diff_base, diff_base_rope)) = diff_base { let buffer_text = buffer.as_rope().to_string(); let mut options = GitOptions::default(); options.context_lines(0); let patch = GitPatch::from_buffers( diff_base.as_bytes(), None, buffer_text.as_bytes(), None, Some(&mut options), ) .log_err(); // A common case in Zed is that the empty buffer is represented as just a newline, // but if we just compute a naive diff you get a "preserved" line in the middle, // which is a bit odd. if buffer_text == "\n" && diff_base.ends_with("\n") && diff_base.len() > 1 { tree.push( InternalDiffHunk { buffer_range: buffer.anchor_before(0)..buffer.anchor_before(0), diff_base_byte_range: 0..diff_base.len() - 1, }, &buffer, ); return tree; } if let Some(patch) = patch { let mut divergence = 0; for hunk_index in 0..patch.num_hunks() { let hunk = process_patch_hunk( &patch, hunk_index, &diff_base_rope, &buffer, &mut divergence, ); tree.push(hunk, &buffer); } } } tree } fn process_patch_hunk( patch: &GitPatch<'_>, hunk_index: usize, diff_base: &Rope, buffer: &text::BufferSnapshot, buffer_row_divergence: &mut i64,