Piotr OsiewiczRust 1.85 (#25272)e4e758d1 year agoHistory
1772 lines · 56.3 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::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.