1869 lines · 59.9 KB · Rust
RawDownload
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.