1517 lines · 49.4 KB · Rust
RawDownload
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.