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::Ordering;use std::{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 struct DiffHunkStatus { pub kind: DiffHunkStatusKind, pub secondary: DiffHunkSecondaryStatus,} #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]pub enum DiffHunkStatusKind { Added, Modified, Deleted,} #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]pub enum DiffHunkSecondaryStatus { HasSecondaryHunk, OverlapsWithSecondaryHunk, None,} impl DiffHunkSecondaryStatus { pub fn is_secondary(&self) -> bool { match self { DiffHunkSecondaryStatus::HasSecondaryHunk => true, DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk => true, DiffHunkSecondaryStatus::None => false, } }} /// 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,} /// 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 sum_tree::SeekTarget<'_, DiffHunkSummary, DiffHunkSummary> for Anchor { fn cmp(&self, cursor_location: &DiffHunkSummary, buffer: &text::BufferSnapshot) -> Ordering { if self .cmp(&cursor_location.buffer_range.start, buffer) .is_lt() { Ordering::Less } else if self.cmp(&cursor_location.buffer_range.end, buffer).is_gt() { Ordering::Greater } else { Ordering::Equal } }} 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) } } } pub fn new_secondary_text_for_stage_or_unstage( &self, stage: bool, hunks: impl Iterator<Item = (Range<Anchor>, Range<usize>)>, buffer: &text::BufferSnapshot, cx: &mut App, ) -> Option<Rope> { let secondary_diff = self.secondary_diff()?; let head_text = self.base_text().map(|text| text.as_rope().clone()); let index_text = secondary_diff .base_text() .map(|text| text.as_rope().clone()); let (index_text, head_text) = match (index_text, head_text) { (Some(index_text), Some(head_text)) => (index_text, head_text), // file is deleted in both index and head (None, None) => return None, // file is deleted in index (None, Some(head_text)) => { return if stage { Some(buffer.as_rope().clone()) } else { Some(head_text) } } // file exists in the index, but is deleted in head (Some(_), None) => { return if stage { Some(buffer.as_rope().clone()) } else { None } } }; let mut secondary_cursor = secondary_diff.inner.hunks.cursor::<DiffHunkSummary>(buffer); secondary_cursor.next(buffer); let mut edits = Vec::new(); let mut prev_secondary_hunk_buffer_offset = 0; let mut prev_secondary_hunk_base_text_offset = 0; for (buffer_range, diff_base_byte_range) in hunks { let skipped_hunks = secondary_cursor.slice(&buffer_range.start, Bias::Left, buffer); if let Some(secondary_hunk) = skipped_hunks.last() { prev_secondary_hunk_base_text_offset = secondary_hunk.diff_base_byte_range.end; prev_secondary_hunk_buffer_offset = secondary_hunk.buffer_range.end.to_offset(buffer); } let mut buffer_offset_range = buffer_range.to_offset(buffer); let start_overshoot = buffer_offset_range.start - prev_secondary_hunk_buffer_offset; let mut secondary_base_text_start = prev_secondary_hunk_base_text_offset + start_overshoot; while let Some(secondary_hunk) = secondary_cursor.item().filter(|item| { item.buffer_range .start .cmp(&buffer_range.end, buffer) .is_le() }) { let secondary_hunk_offset_range = secondary_hunk.buffer_range.to_offset(buffer); prev_secondary_hunk_base_text_offset = secondary_hunk.diff_base_byte_range.end; prev_secondary_hunk_buffer_offset = secondary_hunk_offset_range.end; secondary_base_text_start = secondary_base_text_start.min(secondary_hunk.diff_base_byte_range.start); buffer_offset_range.start = buffer_offset_range .start .min(secondary_hunk_offset_range.start); secondary_cursor.next(buffer); } let end_overshoot = buffer_offset_range .end .saturating_sub(prev_secondary_hunk_buffer_offset); let secondary_base_text_end = prev_secondary_hunk_base_text_offset + end_overshoot; let secondary_base_text_range = secondary_base_text_start..secondary_base_text_end; buffer_offset_range.end = buffer_offset_range .end .max(prev_secondary_hunk_buffer_offset); let replacement_text = if stage { log::debug!("staging"); buffer .text_for_range(buffer_offset_range) .collect::<String>() } else { log::debug!("unstaging"); head_text .chunks_in_range(diff_base_byte_range.clone()) .collect::<String>() }; edits.push((secondary_base_text_range, replacement_text)); } let buffer = cx.new(|cx| { language::Buffer::local_normalized(index_text, text::LineEnding::default(), cx) }); let new_text = buffer.update(cx, |buffer, cx| { buffer.edit(edits, None, cx); buffer.as_rope().clone() }); Some(new_text) }} 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; 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(&start_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; } else if secondary_range.start <= end_point { secondary_status = DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk; } } } else { log::debug!("no secondary cursor!!"); } 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, }); }) } 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, }) }) } 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) { Ordering::Less => { start.get_or_insert(new_hunk.buffer_range.start); end.replace(new_hunk.buffer_range.end); new_cursor.next(new_snapshot); } 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); } 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),Lines 1–500 of 1772. 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::Ordering; use std::{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 struct DiffHunkStatus { pub kind: DiffHunkStatusKind, pub secondary: DiffHunkSecondaryStatus, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum DiffHunkStatusKind { Added, Modified, Deleted, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum DiffHunkSecondaryStatus { HasSecondaryHunk, OverlapsWithSecondaryHunk, None, } impl DiffHunkSecondaryStatus { pub fn is_secondary(&self) -> bool { match self { DiffHunkSecondaryStatus::HasSecondaryHunk => true, DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk => true, DiffHunkSecondaryStatus::None => false, } } } /// 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, } /// 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 sum_tree::SeekTarget<'_, DiffHunkSummary, DiffHunkSummary> for Anchor { fn cmp(&self, cursor_location: &DiffHunkSummary, buffer: &text::BufferSnapshot) -> Ordering { if self .cmp(&cursor_location.buffer_range.start, buffer) .is_lt() { Ordering::Less } else if self.cmp(&cursor_location.buffer_range.end, buffer).is_gt() { Ordering::Greater } else { Ordering::Equal } } } 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) } } } pub fn new_secondary_text_for_stage_or_unstage( &self, stage: bool, hunks: impl Iterator<Item = (Range<Anchor>, Range<usize>)>, buffer: &text::BufferSnapshot, cx: &mut App, ) -> Option<Rope> { let secondary_diff = self.secondary_diff()?; let head_text = self.base_text().map(|text| text.as_rope().clone()); let index_text = secondary_diff .base_text() .map(|text| text.as_rope().clone()); let (index_text, head_text) = match (index_text, head_text) { (Some(index_text), Some(head_text)) => (index_text, head_text), // file is deleted in both index and head (None, None) => return None, // file is deleted in index (None, Some(head_text)) => { return if stage { Some(buffer.as_rope().clone()) } else { Some(head_text) } } // file exists in the index, but is deleted in head (Some(_), None) => { return if stage { Some(buffer.as_rope().clone()) } else { None } } }; let mut secondary_cursor = secondary_diff.inner.hunks.cursor::<DiffHunkSummary>(buffer); secondary_cursor.next(buffer); let mut edits = Vec::new(); let mut prev_secondary_hunk_buffer_offset = 0; let mut prev_secondary_hunk_base_text_offset = 0; for (buffer_range, diff_base_byte_range) in hunks { let skipped_hunks = secondary_cursor.slice(&buffer_range.start, Bias::Left, buffer); if let Some(secondary_hunk) = skipped_hunks.last() { prev_secondary_hunk_base_text_offset = secondary_hunk.diff_base_byte_range.end; prev_secondary_hunk_buffer_offset = secondary_hunk.buffer_range.end.to_offset(buffer); } let mut buffer_offset_range = buffer_range.to_offset(buffer); let start_overshoot = buffer_offset_range.start - prev_secondary_hunk_buffer_offset; let mut secondary_base_text_start = prev_secondary_hunk_base_text_offset + start_overshoot; while let Some(secondary_hunk) = secondary_cursor.item().filter(|item| { item.buffer_range .start .cmp(&buffer_range.end, buffer) .is_le() }) { let secondary_hunk_offset_range = secondary_hunk.buffer_range.to_offset(buffer); prev_secondary_hunk_base_text_offset = secondary_hunk.diff_base_byte_range.end; prev_secondary_hunk_buffer_offset = secondary_hunk_offset_range.end; secondary_base_text_start = secondary_base_text_start.min(secondary_hunk.diff_base_byte_range.start); buffer_offset_range.start = buffer_offset_range .start .min(secondary_hunk_offset_range.start); secondary_cursor.next(buffer); } let end_overshoot = buffer_offset_range .end .saturating_sub(prev_secondary_hunk_buffer_offset); let secondary_base_text_end = prev_secondary_hunk_base_text_offset + end_overshoot; let secondary_base_text_range = secondary_base_text_start..secondary_base_text_end; buffer_offset_range.end = buffer_offset_range .end .max(prev_secondary_hunk_buffer_offset); let replacement_text = if stage { log::debug!("staging"); buffer .text_for_range(buffer_offset_range) .collect::<String>() } else { log::debug!("unstaging"); head_text .chunks_in_range(diff_base_byte_range.clone()) .collect::<String>() }; edits.push((secondary_base_text_range, replacement_text)); } let buffer = cx.new(|cx| { language::Buffer::local_normalized(index_text, text::LineEnding::default(), cx) }); let new_text = buffer.update(cx, |buffer, cx| { buffer.edit(edits, None, cx); buffer.as_rope().clone() }); Some(new_text) } } 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; 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(&start_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; } else if secondary_range.start <= end_point { secondary_status = DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk; } } } else { log::debug!("no secondary cursor!!"); } 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, }); }) } 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, }) }) } 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) { Ordering::Less => { start.get_or_insert(new_hunk.buffer_range.start); end.replace(new_hunk.buffer_range.end); new_cursor.next(new_snapshot); } 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); } 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),