Bennet Bo FennerUpdate comments43983c18 months agoHistory
681 lines · 22.5 KB · Rust
RawDownload
use anyhow::Result;use buffer_diff::{BufferDiff, BufferDiffUpdate};use gpui::{App, AppContext, AsyncApp, Context, Entity, Subscription, Task, WeakEntity};use itertools::Itertools;use language::{    Anchor, Buffer, Capability, DiffOptions, LanguageRegistry, OffsetRangeExt as _, Point,    TextBuffer,};use multi_buffer::{MultiBuffer, PathKey, excerpt_context_lines};use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc};use streaming_diff::LineOperation;use text::{Edit, Patch};use util::ResultExt; pub enum Diff {    Pending(PendingDiff),    Finalized(FinalizedDiff),} impl Diff {    pub fn finalized(        path: String,        old_text: Option<String>,        new_text: String,        language_registry: Arc<LanguageRegistry>,        cx: &mut Context<Self>,    ) -> Self {        let multibuffer = cx.new(|_cx| MultiBuffer::without_headers(Capability::ReadOnly));        let new_buffer = cx.new(|cx| Buffer::local(new_text, cx));        let base_text = old_text.clone().unwrap_or(String::new()).into();        let task = cx.spawn({            let multibuffer = multibuffer.clone();            let path = path.clone();            let buffer = new_buffer.clone();            async move |_, cx| {                let language = language_registry                    .load_language_for_file_path(Path::new(&path))                    .await                    .log_err();                 buffer.update(cx, |buffer, cx| buffer.set_language(language.clone(), cx));                buffer.update(cx, |buffer, _| buffer.parsing_idle()).await;                 let diff = build_buffer_diff(                    old_text.unwrap_or("".into()).into(),                    &buffer,                    Some(language_registry.clone()),                    cx,                )                .await?;                 multibuffer.update(cx, |multibuffer, cx| {                    let hunk_ranges = {                        let buffer = buffer.read(cx);                        diff.read(cx)                            .snapshot(cx)                            .hunks_intersecting_range(                                Anchor::min_for_buffer(buffer.remote_id())                                    ..Anchor::max_for_buffer(buffer.remote_id()),                                buffer,                            )                            .map(|diff_hunk| diff_hunk.buffer_range.to_point(buffer))                            .collect::<Vec<_>>()                    };                     multibuffer.set_excerpts_for_path(                        PathKey::for_buffer(&buffer, cx),                        buffer.clone(),                        hunk_ranges,                        excerpt_context_lines(cx),                        cx,                    );                    multibuffer.add_diff(diff, cx);                });                 anyhow::Ok(())            }        });         Self::Finalized(FinalizedDiff {            multibuffer,            path,            base_text,            new_buffer,            _update_diff: task,        })    }     pub fn new(buffer: Entity<Buffer>, cx: &mut Context<Self>) -> Self {        let subscription = cx.observe(&buffer, |this, _, cx| {            if let Diff::Pending(diff) = this {                diff.auto_update(cx);            }        });        Self::new_inner(            buffer,            UpdateStrategy::Auto {                _subscription: subscription,            },            cx,        )    }     pub fn manual(buffer: Entity<Buffer>, cx: &mut Context<Self>) -> Self {        Self::new_inner(            buffer,            UpdateStrategy::Manual {                pending_update: None,            },            cx,        )    }     fn new_inner(        buffer: Entity<Buffer>,        update_strategy: UpdateStrategy,        cx: &mut Context<Self>,    ) -> Self {        let buffer_text_snapshot = buffer.read(cx).text_snapshot();        let language = buffer.read(cx).language().cloned();        let language_registry = buffer.read(cx).language_registry();        let buffer_diff = cx.new(|cx| {            let mut diff = BufferDiff::new_unchanged(&buffer_text_snapshot, cx);            diff.language_changed(language.clone(), language_registry.clone(), cx);            let secondary_diff = cx.new(|cx| BufferDiff::new_unchanged(&buffer_text_snapshot, cx));            diff.set_secondary_diff(secondary_diff);            diff        });         let multibuffer = cx.new(|cx| {            let mut multibuffer = MultiBuffer::without_headers(Capability::ReadOnly);            multibuffer.set_all_diff_hunks_expanded(cx);            multibuffer.add_diff(buffer_diff.clone(), cx);            multibuffer        });         Self::Pending(PendingDiff {            multibuffer,            base_text: Arc::from(buffer_text_snapshot.text().as_str()),            new_buffer: buffer,            diff: buffer_diff,            revealed_ranges: Vec::new(),            update_strategy,            update_diff: Task::ready(Ok(())),        })    }     pub fn reveal_range(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {        if let Self::Pending(diff) = self {            diff.reveal_range(range, cx);        }    }     pub fn finalize(&mut self, cx: &mut Context<Self>) {        if let Self::Pending(diff) = self {            *self = Self::Finalized(diff.finalize(cx));        }    }     /// Returns the original text before any edits were applied.    pub fn base_text(&self) -> &Arc<str> {        match self {            Self::Pending(PendingDiff { base_text, .. }) => base_text,            Self::Finalized(FinalizedDiff { base_text, .. }) => base_text,        }    }     /// Returns the buffer being edited (for pending diffs) or the snapshot buffer (for finalized diffs).    pub fn buffer(&self) -> &Entity<Buffer> {        match self {            Self::Pending(PendingDiff { new_buffer, .. }) => new_buffer,            Self::Finalized(FinalizedDiff { new_buffer, .. }) => new_buffer,        }    }     pub fn file_path(&self, cx: &App) -> Option<String> {        match self {            Self::Pending(PendingDiff { new_buffer, .. }) => new_buffer                .read(cx)                .file()                .map(|file| file.full_path(cx).to_string_lossy().into_owned()),            Self::Finalized(FinalizedDiff { path, .. }) => Some(path.clone()),        }    }     pub fn multibuffer(&self) -> &Entity<MultiBuffer> {        match self {            Self::Pending(PendingDiff { multibuffer, .. }) => multibuffer,            Self::Finalized(FinalizedDiff { multibuffer, .. }) => multibuffer,        }    }     pub fn to_markdown(&self, cx: &App) -> String {        let buffer_text = self            .multibuffer()            .read(cx)            .all_buffers()            .iter()            .map(|buffer| buffer.read(cx).text())            .join("\n");        let path = match self {            Diff::Pending(PendingDiff {                new_buffer: buffer, ..            }) => buffer                .read(cx)                .file()                .map(|file| file.path().display(file.path_style(cx))),            Diff::Finalized(FinalizedDiff { path, .. }) => Some(path.as_str().into()),        };        format!(            "Diff: {}\n```\n{}\n```\n",            path.unwrap_or("untitled".into()),            buffer_text        )    }     pub fn has_revealed_range(&self, cx: &App) -> bool {        self.multibuffer().read(cx).paths().next().is_some()    }     pub fn needs_update(&self, old_text: &str, new_text: &str, cx: &App) -> bool {        match self {            Diff::Pending(PendingDiff {                base_text,                new_buffer,                ..            }) => {                base_text.as_ref() != old_text                    || !new_buffer.read(cx).as_rope().chunks().equals_str(new_text)            }            Diff::Finalized(FinalizedDiff {                base_text,                new_buffer,                ..            }) => {                base_text.as_ref() != old_text                    || !new_buffer.read(cx).as_rope().chunks().equals_str(new_text)            }        }    }     pub fn push_line_operations(        &mut self,        operations: Vec<LineOperation>,        base_snapshot: text::BufferSnapshot,        cx: &mut Context<Diff>,    ) {        if let Diff::Pending(diff) = self {            diff.push_line_operations(operations, base_snapshot, cx);        }    }} enum UpdateStrategy {    Auto {        _subscription: Subscription,    },    Manual {        pending_update: Option<PendingUpdate>,    },} pub struct PendingDiff {    multibuffer: Entity<MultiBuffer>,    base_text: Arc<str>,    new_buffer: Entity<Buffer>,    diff: Entity<BufferDiff>,    revealed_ranges: Vec<Range<Anchor>>,    update_strategy: UpdateStrategy,    update_diff: Task<Result<()>>,} struct PendingUpdate {    operations: Vec<LineOperation>,    base_snapshot: text::BufferSnapshot,    text_snapshot: text::BufferSnapshot,} fn compute_hunks(    diff_base: &text::BufferSnapshot,    buffer: &text::BufferSnapshot,    line_operations: Vec<LineOperation>,) -> Patch<usize> {    let mut patch = Patch::default();     let mut old_row = 0u32;    let mut new_row = 0u32;     // Merge adjacent Delete+Insert into a single Modified hunk    let mut pending_delete_lines: Option<u32> = None;     let flush_delete = |pending_delete_lines: &mut Option<u32>,                        old_row: &mut u32,                        new_row: u32,                        diff_base: &text::BufferSnapshot,                        buffer: &text::BufferSnapshot| {        if let Some(deleted_lines) = pending_delete_lines.take() {            let old_start =                diff_base.point_to_offset(Point::new(*old_row, 0).min(diff_base.max_point()));            let old_end = diff_base.point_to_offset(                Point::new(*old_row + deleted_lines, 0).min(diff_base.max_point()),            );            let new_pos = buffer.point_to_offset(Point::new(new_row, 0).min(buffer.max_point()));            let edit = Edit {                old: old_start..old_end,                new: new_pos..new_pos,            };            *old_row += deleted_lines;            Some(edit)        } else {            None        }    };     for operation in line_operations {        match operation {            LineOperation::Delete { lines } => {                // Accumulate deletions — they might be followed by an Insert (= modification)                *pending_delete_lines.get_or_insert(0) += lines;            }            LineOperation::Insert { lines } => {                let old_start =                    diff_base.point_to_offset(Point::new(old_row, 0).min(diff_base.max_point()));                let (old_end, deleted_lines) =                    if let Some(deleted_lines) = pending_delete_lines.take() {                        // Delete followed by Insert = Modified hunk                        let old_end = diff_base.point_to_offset(                            Point::new(old_row + deleted_lines, 0).min(diff_base.max_point()),                        );                        (old_end, deleted_lines)                    } else {                        // Pure insertion                        (old_start, 0)                    };                let new_start =                    buffer.point_to_offset(Point::new(new_row, 0).min(buffer.max_point()));                let new_end =                    buffer.point_to_offset(Point::new(new_row + lines, 0).min(buffer.max_point()));                patch.push(Edit {                    old: old_start..old_end,                    new: new_start..new_end,                });                old_row += deleted_lines;                new_row += lines;            }            LineOperation::Keep { lines } => {                // Flush any pending deletion before a Keep                if let Some(edit) = flush_delete(                    &mut pending_delete_lines,                    &mut old_row,                    new_row,                    diff_base,                    buffer,                ) {                    patch.push(edit);                }                // Keep = unchanged, no hunk to push                old_row += lines;                new_row += lines;            }        }    }     // Flush any trailing deletion    if let Some(edit) = flush_delete(        &mut pending_delete_lines,        &mut old_row,        new_row,        diff_base,        buffer,    ) {        patch.push(edit);    }     patch} /// Applies a `BufferDiffUpdate` to both the primary and secondary diffs, then/// refreshes the visible excerpt ranges in the multibuffer.async fn apply_diff_update(    update: BufferDiffUpdate,    text_snapshot: &text::BufferSnapshot,    buffer_diff: &Entity<BufferDiff>,    diff: &WeakEntity<Diff>,    cx: &mut AsyncApp,) -> Result<()> {    // todo: we can get away without having a whole secondary diff here    let (task1, task2) = buffer_diff.update(cx, |diff, cx| {        let task1 = diff.set_snapshot(update.clone(), text_snapshot, cx);        let task2 = diff            .secondary_diff()            .expect("PendingDiff should always have a secondary diff")            .update(cx, |diff, cx| diff.set_snapshot(update, text_snapshot, cx));        (task1, task2)    });    task1.await;    task2.await;    diff.update(cx, |diff, cx| {        if let Diff::Pending(diff) = diff {            diff.update_visible_ranges(cx);        }    })} impl PendingDiff {    fn push_line_operations(        &mut self,        operations: Vec<LineOperation>,        base_snapshot: text::BufferSnapshot,        cx: &mut Context<Diff>,    ) {        let UpdateStrategy::Manual { pending_update } = &mut self.update_strategy else {            return;        };         let text_snapshot = self.new_buffer.read(cx).text_snapshot();        *pending_update = Some(PendingUpdate {            operations,            base_snapshot,            text_snapshot,        });        if self.update_diff.is_ready() {            self.flush_pending_update(cx);        }    }     fn auto_update(&mut self, cx: &mut Context<Diff>) {        let buffer = self.new_buffer.clone();        let buffer_diff = self.diff.clone();        let base_text = self.base_text.clone();        self.update_diff = cx.spawn(async move |diff, cx| {            let text_snapshot = buffer.read_with(cx, |buffer, _| buffer.text_snapshot());            let language = buffer.read_with(cx, |buffer, _| buffer.language().cloned());            let update = buffer_diff                .update(cx, |diff, cx| {                    diff.update_diff(                        text_snapshot.clone(),                        Some(base_text.clone()),                        None,                        language,                        cx,                    )                })                .await;            apply_diff_update(update, &text_snapshot, &buffer_diff, &diff, cx).await        });    }     fn flush_pending_update(&mut self, cx: &mut Context<Diff>) {        let UpdateStrategy::Manual { pending_update } = &mut self.update_strategy else {            return;        };         let Some(PendingUpdate {            operations,            base_snapshot,            text_snapshot,        }) = pending_update.take()        else {            return;        };         let buffer_diff = self.diff.clone();        let base_text = self.base_text.clone();        self.update_diff = cx.spawn(async move |diff, cx| {            let update = cx                .background_spawn({                    let snapshot = text_snapshot.clone();                    async move {                        let hunks = compute_hunks(&base_snapshot, &snapshot, operations);                        BufferDiffUpdate::from_hunks(                            base_text,                            base_snapshot.as_rope(),                            snapshot,                            hunks,                            Some(DiffOptions::default()),                        )                    }                })                .await;             apply_diff_update(update, &text_snapshot, &buffer_diff, &diff, cx).await?;             diff.update(cx, |diff, cx| {                if let Diff::Pending(diff) = diff {                    // Pick up any update that arrived while this task was running.                    diff.flush_pending_update(cx);                }            })        });    }     pub fn reveal_range(&mut self, range: Range<Anchor>, cx: &mut Context<Diff>) {        self.revealed_ranges.push(range);        self.update_visible_ranges(cx);    }     fn finalize(&self, cx: &mut Context<Diff>) -> FinalizedDiff {        let ranges = self.excerpt_ranges(cx);        let base_text = self.base_text.clone();

Lines 1–500 of 681.