use futures::channel::oneshot;use git2::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as GitPatch};use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task};use language::{Language, LanguageRegistry};use rope::Rope;use std::cmp::Ordering;use std::mem;use std::{future::Future, iter, ops::Range, sync::Arc};use sum_tree::{SumTree, TreeMap};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>>,} #[derive(Clone)]struct BufferDiffInner { hunks: SumTree<InternalDiffHunk>, pending_hunks: TreeMap<usize, PendingHunk>, base_text: language::BufferSnapshot, base_text_exists: bool,} #[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, SecondaryHunkAdditionPending, SecondaryHunkRemovalPending,} /// A diff hunk resolved to rows in the buffer.#[derive(Debug, Clone, PartialEq, Eq)]pub struct DiffHunk { /// The buffer range as points. pub range: Range<Point>, /// 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>,} #[derive(Debug, Clone, PartialEq, Eq)]struct PendingHunk { buffer_version: clock::Global, new_status: DiffHunkSecondaryStatus,} #[derive(Debug, Default, Clone)]pub struct DiffHunkSummary { buffer_range: Range<Anchor>,} impl sum_tree::Item for InternalDiffHunk { type Summary = DiffHunkSummary; fn summary(&self, _cx: &text::BufferSnapshot) -> Self::Summary { DiffHunkSummary { buffer_range: self.buffer_range.clone(), } }} 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) -> &language::BufferSnapshot { &self.inner.base_text } pub fn base_texts_eq(&self, other: &Self) -> bool { if self.inner.base_text_exists != other.inner.base_text_exists { return false; } let left = &self.inner.base_text; let right = &other.inner.base_text; let (old_id, old_empty) = (left.remote_id(), left.is_empty()); let (new_id, new_empty) = (right.remote_id(), right.is_empty()); new_id == old_id || (new_empty && old_empty) }} impl BufferDiffInner { fn stage_or_unstage_hunks( &mut self, unstaged_diff: &Self, stage: bool, hunks: &[DiffHunk], buffer: &text::BufferSnapshot, file_exists: bool, ) -> (Option<Rope>, Vec<(usize, PendingHunk)>) { let head_text = self .base_text_exists .then(|| self.base_text.as_rope().clone()); let index_text = unstaged_diff .base_text_exists .then(|| unstaged_diff.base_text.as_rope().clone()); // If the file doesn't exist in either HEAD or the index, then the // entire file must be either created or deleted in the index. let (index_text, head_text) = match (index_text, head_text) { (Some(index_text), Some(head_text)) if file_exists || !stage => (index_text, head_text), (_, head_text @ _) => { if stage { log::debug!("stage all"); return ( file_exists.then(|| buffer.as_rope().clone()), vec![( 0, PendingHunk { buffer_version: buffer.version().clone(), new_status: DiffHunkSecondaryStatus::SecondaryHunkRemovalPending, }, )], ); } else { log::debug!("unstage all"); return ( head_text, vec![( 0, PendingHunk { buffer_version: buffer.version().clone(), new_status: DiffHunkSecondaryStatus::SecondaryHunkAdditionPending, }, )], ); } } }; let mut unstaged_hunk_cursor = unstaged_diff.hunks.cursor::<DiffHunkSummary>(buffer); unstaged_hunk_cursor.next(buffer); let mut edits = Vec::new(); let mut pending_hunks = Vec::new(); let mut prev_unstaged_hunk_buffer_offset = 0; let mut prev_unstaged_hunk_base_text_offset = 0; for DiffHunk { buffer_range, diff_base_byte_range, secondary_status, .. } in hunks.iter().cloned() { if (stage && secondary_status == DiffHunkSecondaryStatus::None) || (!stage && secondary_status == DiffHunkSecondaryStatus::HasSecondaryHunk) { continue; } let skipped_hunks = unstaged_hunk_cursor.slice(&buffer_range.start, Bias::Left, buffer); if let Some(secondary_hunk) = skipped_hunks.last() { prev_unstaged_hunk_base_text_offset = secondary_hunk.diff_base_byte_range.end; prev_unstaged_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_unstaged_hunk_buffer_offset; let mut index_start = prev_unstaged_hunk_base_text_offset + start_overshoot; while let Some(unstaged_hunk) = unstaged_hunk_cursor.item().filter(|item| { item.buffer_range .start .cmp(&buffer_range.end, buffer) .is_le() }) { let unstaged_hunk_offset_range = unstaged_hunk.buffer_range.to_offset(buffer); prev_unstaged_hunk_base_text_offset = unstaged_hunk.diff_base_byte_range.end; prev_unstaged_hunk_buffer_offset = unstaged_hunk_offset_range.end; index_start = index_start.min(unstaged_hunk.diff_base_byte_range.start); buffer_offset_range.start = buffer_offset_range .start .min(unstaged_hunk_offset_range.start); unstaged_hunk_cursor.next(buffer); } let end_overshoot = buffer_offset_range .end .saturating_sub(prev_unstaged_hunk_buffer_offset); let index_end = prev_unstaged_hunk_base_text_offset + end_overshoot; let index_range = index_start..index_end; buffer_offset_range.end = buffer_offset_range .end .max(prev_unstaged_hunk_buffer_offset); let replacement_text = if stage { log::debug!("stage hunk {:?}", buffer_offset_range); buffer .text_for_range(buffer_offset_range) .collect::<String>() } else { log::debug!("unstage hunk {:?}", buffer_offset_range); head_text .chunks_in_range(diff_base_byte_range.clone()) .collect::<String>() }; pending_hunks.push(( diff_base_byte_range.start, PendingHunk { buffer_version: buffer.version().clone(), new_status: if stage { DiffHunkSecondaryStatus::SecondaryHunkRemovalPending } else { DiffHunkSecondaryStatus::SecondaryHunkAdditionPending }, }, )); edits.push((index_range, replacement_text)); } let mut new_index_text = Rope::new(); let mut index_cursor = index_text.cursor(0); for (old_range, replacement_text) in edits { new_index_text.append(index_cursor.slice(old_range.start)); index_cursor.seek_forward(old_range.end); new_index_text.push(&replacement_text); } new_index_text.append(index_cursor.suffix()); (Some(new_index_text), pending_hunks) } 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 = None; let mut pending_hunks = TreeMap::default(); if let Some(secondary) = secondary.as_ref() { let mut cursor = secondary.hunks.cursor::<DiffHunkSummary>(buffer); cursor.next(buffer); secondary_cursor = Some(cursor); pending_hunks = secondary.pending_hunks.clone(); } let max_point = buffer.max_point(); 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 < max_point { end_point.row += 1; end_point.column = 0; end_anchor = buffer.anchor_before(end_point); } let mut secondary_status = DiffHunkSecondaryStatus::None; let mut has_pending = false; if let Some(pending_hunk) = pending_hunks.get(&start_base) { if !buffer.has_edits_since_in_range( &pending_hunk.buffer_version, start_anchor..end_anchor, ) { has_pending = true; secondary_status = pending_hunk.new_status; } } if let (Some(secondary_cursor), false) = (secondary_cursor.as_mut(), has_pending) { 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.is_empty() && secondary_hunk.diff_base_byte_range.is_empty() { // ignore } else if secondary_range == (start_point..end_point) { secondary_status = DiffHunkSecondaryStatus::HasSecondaryHunk; } else if secondary_range.start <= end_point { secondary_status = DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk; } } } return Some(DiffHunk { range: start_point..end_point, 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); Some(DiffHunk { range, 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); }Lines 1–500 of 1869. Load more
use futures::channel::oneshot; use git2::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as GitPatch}; use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task}; use language::{Language, LanguageRegistry}; use rope::Rope; use std::cmp::Ordering; use std::mem; use std::{future::Future, iter, ops::Range, sync::Arc}; use sum_tree::{SumTree, TreeMap}; 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>>, } #[derive(Clone)] struct BufferDiffInner { hunks: SumTree<InternalDiffHunk>, pending_hunks: TreeMap<usize, PendingHunk>, base_text: language::BufferSnapshot, base_text_exists: bool, } #[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, SecondaryHunkAdditionPending, SecondaryHunkRemovalPending, } /// A diff hunk resolved to rows in the buffer. #[derive(Debug, Clone, PartialEq, Eq)] pub struct DiffHunk { /// The buffer range as points. pub range: Range<Point>, /// 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>, } #[derive(Debug, Clone, PartialEq, Eq)] struct PendingHunk { buffer_version: clock::Global, new_status: DiffHunkSecondaryStatus, } #[derive(Debug, Default, Clone)] pub struct DiffHunkSummary { buffer_range: Range<Anchor>, } impl sum_tree::Item for InternalDiffHunk { type Summary = DiffHunkSummary; fn summary(&self, _cx: &text::BufferSnapshot) -> Self::Summary { DiffHunkSummary { buffer_range: self.buffer_range.clone(), } } } 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) -> &language::BufferSnapshot { &self.inner.base_text } pub fn base_texts_eq(&self, other: &Self) -> bool { if self.inner.base_text_exists != other.inner.base_text_exists { return false; } let left = &self.inner.base_text; let right = &other.inner.base_text; let (old_id, old_empty) = (left.remote_id(), left.is_empty()); let (new_id, new_empty) = (right.remote_id(), right.is_empty()); new_id == old_id || (new_empty && old_empty) } } impl BufferDiffInner { fn stage_or_unstage_hunks( &mut self, unstaged_diff: &Self, stage: bool, hunks: &[DiffHunk], buffer: &text::BufferSnapshot, file_exists: bool, ) -> (Option<Rope>, Vec<(usize, PendingHunk)>) { let head_text = self .base_text_exists .then(|| self.base_text.as_rope().clone()); let index_text = unstaged_diff .base_text_exists .then(|| unstaged_diff.base_text.as_rope().clone()); // If the file doesn't exist in either HEAD or the index, then the // entire file must be either created or deleted in the index. let (index_text, head_text) = match (index_text, head_text) { (Some(index_text), Some(head_text)) if file_exists || !stage => (index_text, head_text), (_, head_text @ _) => { if stage { log::debug!("stage all"); return ( file_exists.then(|| buffer.as_rope().clone()), vec![( 0, PendingHunk { buffer_version: buffer.version().clone(), new_status: DiffHunkSecondaryStatus::SecondaryHunkRemovalPending, }, )], ); } else { log::debug!("unstage all"); return ( head_text, vec![( 0, PendingHunk { buffer_version: buffer.version().clone(), new_status: DiffHunkSecondaryStatus::SecondaryHunkAdditionPending, }, )], ); } } }; let mut unstaged_hunk_cursor = unstaged_diff.hunks.cursor::<DiffHunkSummary>(buffer); unstaged_hunk_cursor.next(buffer); let mut edits = Vec::new(); let mut pending_hunks = Vec::new(); let mut prev_unstaged_hunk_buffer_offset = 0; let mut prev_unstaged_hunk_base_text_offset = 0; for DiffHunk { buffer_range, diff_base_byte_range, secondary_status, .. } in hunks.iter().cloned() { if (stage && secondary_status == DiffHunkSecondaryStatus::None) || (!stage && secondary_status == DiffHunkSecondaryStatus::HasSecondaryHunk) { continue; } let skipped_hunks = unstaged_hunk_cursor.slice(&buffer_range.start, Bias::Left, buffer); if let Some(secondary_hunk) = skipped_hunks.last() { prev_unstaged_hunk_base_text_offset = secondary_hunk.diff_base_byte_range.end; prev_unstaged_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_unstaged_hunk_buffer_offset; let mut index_start = prev_unstaged_hunk_base_text_offset + start_overshoot; while let Some(unstaged_hunk) = unstaged_hunk_cursor.item().filter(|item| { item.buffer_range .start .cmp(&buffer_range.end, buffer) .is_le() }) { let unstaged_hunk_offset_range = unstaged_hunk.buffer_range.to_offset(buffer); prev_unstaged_hunk_base_text_offset = unstaged_hunk.diff_base_byte_range.end; prev_unstaged_hunk_buffer_offset = unstaged_hunk_offset_range.end; index_start = index_start.min(unstaged_hunk.diff_base_byte_range.start); buffer_offset_range.start = buffer_offset_range .start .min(unstaged_hunk_offset_range.start); unstaged_hunk_cursor.next(buffer); } let end_overshoot = buffer_offset_range .end .saturating_sub(prev_unstaged_hunk_buffer_offset); let index_end = prev_unstaged_hunk_base_text_offset + end_overshoot; let index_range = index_start..index_end; buffer_offset_range.end = buffer_offset_range .end .max(prev_unstaged_hunk_buffer_offset); let replacement_text = if stage { log::debug!("stage hunk {:?}", buffer_offset_range); buffer .text_for_range(buffer_offset_range) .collect::<String>() } else { log::debug!("unstage hunk {:?}", buffer_offset_range); head_text .chunks_in_range(diff_base_byte_range.clone()) .collect::<String>() }; pending_hunks.push(( diff_base_byte_range.start, PendingHunk { buffer_version: buffer.version().clone(), new_status: if stage { DiffHunkSecondaryStatus::SecondaryHunkRemovalPending } else { DiffHunkSecondaryStatus::SecondaryHunkAdditionPending }, }, )); edits.push((index_range, replacement_text)); } let mut new_index_text = Rope::new(); let mut index_cursor = index_text.cursor(0); for (old_range, replacement_text) in edits { new_index_text.append(index_cursor.slice(old_range.start)); index_cursor.seek_forward(old_range.end); new_index_text.push(&replacement_text); } new_index_text.append(index_cursor.suffix()); (Some(new_index_text), pending_hunks) } 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 = None; let mut pending_hunks = TreeMap::default(); if let Some(secondary) = secondary.as_ref() { let mut cursor = secondary.hunks.cursor::<DiffHunkSummary>(buffer); cursor.next(buffer); secondary_cursor = Some(cursor); pending_hunks = secondary.pending_hunks.clone(); } let max_point = buffer.max_point(); 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 < max_point { end_point.row += 1; end_point.column = 0; end_anchor = buffer.anchor_before(end_point); } let mut secondary_status = DiffHunkSecondaryStatus::None; let mut has_pending = false; if let Some(pending_hunk) = pending_hunks.get(&start_base) { if !buffer.has_edits_since_in_range( &pending_hunk.buffer_version, start_anchor..end_anchor, ) { has_pending = true; secondary_status = pending_hunk.new_status; } } if let (Some(secondary_cursor), false) = (secondary_cursor.as_mut(), has_pending) { 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.is_empty() && secondary_hunk.diff_base_byte_range.is_empty() { // ignore } else if secondary_range == (start_point..end_point) { secondary_status = DiffHunkSecondaryStatus::HasSecondaryHunk; } else if secondary_range.start <= end_point { secondary_status = DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk; } } } return Some(DiffHunk { range: start_point..end_point, 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); Some(DiffHunk { range, 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); }