1401 lines · 51.5 KB · Rust
RawDownload
use anyhow::{Context as _, Result};use buffer_diff::BufferDiff;use collections::BTreeMap;use futures::{StreamExt, channel::mpsc};use gpui::{App, AppContext, AsyncApp, Context, Entity, Subscription, Task, WeakEntity};use language::{Anchor, Buffer, BufferEvent, DiskState, Point};use project::{Project, ProjectItem};use std::{cmp, ops::Range, sync::Arc};use text::{Edit, Patch, Rope};use util::RangeExt; /// Tracks actions performed by tools in a threadpub struct ActionLog {    /// Buffers that we want to notify the model about when they change.    tracked_buffers: BTreeMap<Entity<Buffer>, TrackedBuffer>,    /// Has the model edited a file since it last checked diagnostics?    edited_since_project_diagnostics_check: bool,    /// The project this action log is associated with    project: Entity<Project>,} impl ActionLog {    /// Creates a new, empty action log associated with the given project.    pub fn new(project: Entity<Project>) -> Self {        Self {            tracked_buffers: BTreeMap::default(),            edited_since_project_diagnostics_check: false,            project,        }    }     /// Notifies a diagnostics check    pub fn checked_project_diagnostics(&mut self) {        self.edited_since_project_diagnostics_check = false;    }     /// Returns true if any files have been edited since the last project diagnostics check    pub fn has_edited_files_since_project_diagnostics_check(&self) -> bool {        self.edited_since_project_diagnostics_check    }     fn track_buffer(        &mut self,        buffer: Entity<Buffer>,        created: bool,        cx: &mut Context<Self>,    ) -> &mut TrackedBuffer {        let tracked_buffer = self            .tracked_buffers            .entry(buffer.clone())            .or_insert_with(|| {                let text_snapshot = buffer.read(cx).text_snapshot();                let diff = cx.new(|cx| BufferDiff::new(&text_snapshot, cx));                let (diff_update_tx, diff_update_rx) = mpsc::unbounded();                let base_text;                let status;                let unreviewed_changes;                if created {                    base_text = Rope::default();                    status = TrackedBufferStatus::Created;                    unreviewed_changes = Patch::new(vec![Edit {                        old: 0..1,                        new: 0..text_snapshot.max_point().row + 1,                    }])                } else {                    base_text = buffer.read(cx).as_rope().clone();                    status = TrackedBufferStatus::Modified;                    unreviewed_changes = Patch::default();                }                TrackedBuffer {                    buffer: buffer.clone(),                    base_text,                    unreviewed_changes,                    snapshot: text_snapshot.clone(),                    status,                    version: buffer.read(cx).version(),                    diff,                    diff_update: diff_update_tx,                    _maintain_diff: cx.spawn({                        let buffer = buffer.clone();                        async move |this, cx| {                            Self::maintain_diff(this, buffer, diff_update_rx, cx)                                .await                                .ok();                        }                    }),                    _subscription: cx.subscribe(&buffer, Self::handle_buffer_event),                }            });        tracked_buffer.version = buffer.read(cx).version();        tracked_buffer    }     fn handle_buffer_event(        &mut self,        buffer: Entity<Buffer>,        event: &BufferEvent,        cx: &mut Context<Self>,    ) {        match event {            BufferEvent::Edited { .. } => self.handle_buffer_edited(buffer, cx),            BufferEvent::FileHandleChanged => {                self.handle_buffer_file_changed(buffer, cx);            }            _ => {}        };    }     fn handle_buffer_edited(&mut self, buffer: Entity<Buffer>, cx: &mut Context<Self>) {        let Some(tracked_buffer) = self.tracked_buffers.get_mut(&buffer) else {            return;        };        tracked_buffer.schedule_diff_update(ChangeAuthor::User, cx);    }     fn handle_buffer_file_changed(&mut self, buffer: Entity<Buffer>, cx: &mut Context<Self>) {        let Some(tracked_buffer) = self.tracked_buffers.get_mut(&buffer) else {            return;        };         match tracked_buffer.status {            TrackedBufferStatus::Created | TrackedBufferStatus::Modified => {                if buffer                    .read(cx)                    .file()                    .map_or(false, |file| file.disk_state() == DiskState::Deleted)                {                    // If the buffer had been edited by a tool, but it got                    // deleted externally, we want to stop tracking it.                    self.tracked_buffers.remove(&buffer);                }                cx.notify();            }            TrackedBufferStatus::Deleted => {                if buffer                    .read(cx)                    .file()                    .map_or(false, |file| file.disk_state() != DiskState::Deleted)                {                    // If the buffer had been deleted by a tool, but it got                    // resurrected externally, we want to clear the changes we                    // were tracking and reset the buffer's state.                    self.tracked_buffers.remove(&buffer);                    self.track_buffer(buffer, false, cx);                }                cx.notify();            }        }    }     async fn maintain_diff(        this: WeakEntity<Self>,        buffer: Entity<Buffer>,        mut diff_update: mpsc::UnboundedReceiver<(ChangeAuthor, text::BufferSnapshot)>,        cx: &mut AsyncApp,    ) -> Result<()> {        while let Some((author, buffer_snapshot)) = diff_update.next().await {            let (rebase, diff, language, language_registry) =                this.read_with(cx, |this, cx| {                    let tracked_buffer = this                        .tracked_buffers                        .get(&buffer)                        .context("buffer not tracked")?;                     let rebase = cx.background_spawn({                        let mut base_text = tracked_buffer.base_text.clone();                        let old_snapshot = tracked_buffer.snapshot.clone();                        let new_snapshot = buffer_snapshot.clone();                        let unreviewed_changes = tracked_buffer.unreviewed_changes.clone();                        async move {                            let edits = diff_snapshots(&old_snapshot, &new_snapshot);                            if let ChangeAuthor::User = author {                                apply_non_conflicting_edits(                                    &unreviewed_changes,                                    edits,                                    &mut base_text,                                    new_snapshot.as_rope(),                                );                            }                            (Arc::new(base_text.to_string()), base_text)                        }                    });                     anyhow::Ok((                        rebase,                        tracked_buffer.diff.clone(),                        tracked_buffer.buffer.read(cx).language().cloned(),                        tracked_buffer.buffer.read(cx).language_registry(),                    ))                })??;             let (new_base_text, new_base_text_rope) = rebase.await;            let diff_snapshot = BufferDiff::update_diff(                diff.clone(),                buffer_snapshot.clone(),                Some(new_base_text),                true,                false,                language,                language_registry,                cx,            )            .await;             let mut unreviewed_changes = Patch::default();            if let Ok(diff_snapshot) = diff_snapshot {                unreviewed_changes = cx                    .background_spawn({                        let diff_snapshot = diff_snapshot.clone();                        let buffer_snapshot = buffer_snapshot.clone();                        let new_base_text_rope = new_base_text_rope.clone();                        async move {                            let mut unreviewed_changes = Patch::default();                            for hunk in diff_snapshot.hunks_intersecting_range(                                Anchor::MIN..Anchor::MAX,                                &buffer_snapshot,                            ) {                                let old_range = new_base_text_rope                                    .offset_to_point(hunk.diff_base_byte_range.start)                                    ..new_base_text_rope                                        .offset_to_point(hunk.diff_base_byte_range.end);                                let new_range = hunk.range.start..hunk.range.end;                                unreviewed_changes.push(point_to_row_edit(                                    Edit {                                        old: old_range,                                        new: new_range,                                    },                                    &new_base_text_rope,                                    &buffer_snapshot.as_rope(),                                ));                            }                            unreviewed_changes                        }                    })                    .await;                 diff.update(cx, |diff, cx| {                    diff.set_snapshot(diff_snapshot, &buffer_snapshot, None, cx)                })?;            }            this.update(cx, |this, cx| {                let tracked_buffer = this                    .tracked_buffers                    .get_mut(&buffer)                    .context("buffer not tracked")?;                tracked_buffer.base_text = new_base_text_rope;                tracked_buffer.snapshot = buffer_snapshot;                tracked_buffer.unreviewed_changes = unreviewed_changes;                cx.notify();                anyhow::Ok(())            })??;        }         Ok(())    }     /// Track a buffer as read, so we can notify the model about user edits.    pub fn buffer_read(&mut self, buffer: Entity<Buffer>, cx: &mut Context<Self>) {        self.track_buffer(buffer, false, cx);    }     /// Track a buffer that was added as context, so we can notify the model about user edits.    pub fn buffer_added_as_context(&mut self, buffer: Entity<Buffer>, cx: &mut Context<Self>) {        self.track_buffer(buffer, false, cx);    }     /// Track a buffer as read, so we can notify the model about user edits.    pub fn will_create_buffer(&mut self, buffer: Entity<Buffer>, cx: &mut Context<Self>) {        self.track_buffer(buffer.clone(), true, cx);        self.buffer_edited(buffer, cx)    }     /// Mark a buffer as edited, so we can refresh it in the context    pub fn buffer_edited(&mut self, buffer: Entity<Buffer>, cx: &mut Context<Self>) {        self.edited_since_project_diagnostics_check = true;         let tracked_buffer = self.track_buffer(buffer.clone(), false, cx);        if let TrackedBufferStatus::Deleted = tracked_buffer.status {            tracked_buffer.status = TrackedBufferStatus::Modified;        }        tracked_buffer.schedule_diff_update(ChangeAuthor::Agent, cx);    }     pub fn will_delete_buffer(&mut self, buffer: Entity<Buffer>, cx: &mut Context<Self>) {        let tracked_buffer = self.track_buffer(buffer.clone(), false, cx);        match tracked_buffer.status {            TrackedBufferStatus::Created => {                self.tracked_buffers.remove(&buffer);                cx.notify();            }            TrackedBufferStatus::Modified => {                buffer.update(cx, |buffer, cx| buffer.set_text("", cx));                tracked_buffer.status = TrackedBufferStatus::Deleted;                tracked_buffer.schedule_diff_update(ChangeAuthor::Agent, cx);            }            TrackedBufferStatus::Deleted => {}        }        cx.notify();    }     pub fn keep_edits_in_range(        &mut self,        buffer: Entity<Buffer>,        buffer_range: Range<impl language::ToPoint>,        cx: &mut Context<Self>,    ) {        let Some(tracked_buffer) = self.tracked_buffers.get_mut(&buffer) else {            return;        };         match tracked_buffer.status {            TrackedBufferStatus::Deleted => {                self.tracked_buffers.remove(&buffer);                cx.notify();            }            _ => {                let buffer = buffer.read(cx);                let buffer_range =                    buffer_range.start.to_point(buffer)..buffer_range.end.to_point(buffer);                let mut delta = 0i32;                 tracked_buffer.unreviewed_changes.retain_mut(|edit| {                    edit.old.start = (edit.old.start as i32 + delta) as u32;                    edit.old.end = (edit.old.end as i32 + delta) as u32;                     if buffer_range.end.row < edit.new.start                        || buffer_range.start.row > edit.new.end                    {                        true                    } else {                        let old_range = tracked_buffer                            .base_text                            .point_to_offset(Point::new(edit.old.start, 0))                            ..tracked_buffer.base_text.point_to_offset(cmp::min(                                Point::new(edit.old.end, 0),                                tracked_buffer.base_text.max_point(),                            ));                        let new_range = tracked_buffer                            .snapshot                            .point_to_offset(Point::new(edit.new.start, 0))                            ..tracked_buffer.snapshot.point_to_offset(cmp::min(                                Point::new(edit.new.end, 0),                                tracked_buffer.snapshot.max_point(),                            ));                        tracked_buffer.base_text.replace(                            old_range,                            &tracked_buffer                                .snapshot                                .text_for_range(new_range)                                .collect::<String>(),                        );                        delta += edit.new_len() as i32 - edit.old_len() as i32;                        false                    }                });                tracked_buffer.schedule_diff_update(ChangeAuthor::User, cx);            }        }    }     pub fn reject_edits_in_range(        &mut self,        buffer: Entity<Buffer>,        buffer_range: Range<impl language::ToPoint>,        cx: &mut Context<Self>,    ) -> Task<Result<()>> {        let Some(tracked_buffer) = self.tracked_buffers.get_mut(&buffer) else {            return Task::ready(Ok(()));        };         match tracked_buffer.status {            TrackedBufferStatus::Created => {                let delete = buffer                    .read(cx)                    .entry_id(cx)                    .and_then(|entry_id| {                        self.project                            .update(cx, |project, cx| project.delete_entry(entry_id, false, cx))                    })                    .unwrap_or(Task::ready(Ok(())));                self.tracked_buffers.remove(&buffer);                cx.notify();                delete            }            TrackedBufferStatus::Deleted => {                buffer.update(cx, |buffer, cx| {                    buffer.set_text(tracked_buffer.base_text.to_string(), cx)                });                let save = self                    .project                    .update(cx, |project, cx| project.save_buffer(buffer.clone(), cx));                 // Clear all tracked changes for this buffer and start over as if we just read it.                self.tracked_buffers.remove(&buffer);                self.track_buffer(buffer.clone(), false, cx);                cx.notify();                save            }            TrackedBufferStatus::Modified => {                buffer.update(cx, |buffer, cx| {                    let buffer_range =                        buffer_range.start.to_point(buffer)..buffer_range.end.to_point(buffer);                     let mut edits_to_revert = Vec::new();                    for edit in tracked_buffer.unreviewed_changes.edits() {                        if buffer_range.end.row < edit.new.start {                            break;                        } else if buffer_range.start.row > edit.new.end {                            continue;                        }                         let old_range = tracked_buffer                            .base_text                            .point_to_offset(Point::new(edit.old.start, 0))                            ..tracked_buffer.base_text.point_to_offset(cmp::min(                                Point::new(edit.old.end, 0),                                tracked_buffer.base_text.max_point(),                            ));                        let old_text = tracked_buffer                            .base_text                            .chunks_in_range(old_range)                            .collect::<String>();                         let new_range = tracked_buffer                            .snapshot                            .anchor_before(Point::new(edit.new.start, 0))                            ..tracked_buffer.snapshot.anchor_after(cmp::min(                                Point::new(edit.new.end, 0),                                tracked_buffer.snapshot.max_point(),                            ));                        edits_to_revert.push((new_range, old_text));                    }                     buffer.edit(edits_to_revert, None, cx);                });                self.project                    .update(cx, |project, cx| project.save_buffer(buffer, cx))            }        }    }     pub fn keep_all_edits(&mut self, cx: &mut Context<Self>) {        self.tracked_buffers            .retain(|_buffer, tracked_buffer| match tracked_buffer.status {                TrackedBufferStatus::Deleted => false,                _ => {                    tracked_buffer.unreviewed_changes.clear();                    tracked_buffer.base_text = tracked_buffer.snapshot.as_rope().clone();                    tracked_buffer.schedule_diff_update(ChangeAuthor::User, cx);                    true                }            });        cx.notify();    }     /// Returns the set of buffers that contain changes that haven't been reviewed by the user.    pub fn changed_buffers(&self, cx: &App) -> BTreeMap<Entity<Buffer>, Entity<BufferDiff>> {        self.tracked_buffers            .iter()            .filter(|(_, tracked)| tracked.has_changes(cx))            .map(|(buffer, tracked)| (buffer.clone(), tracked.diff.clone()))            .collect()    }     /// Iterate over buffers changed since last read or edited by the model    pub fn stale_buffers<'a>(&'a self, cx: &'a App) -> impl Iterator<Item = &'a Entity<Buffer>> {        self.tracked_buffers            .iter()            .filter(|(buffer, tracked)| {                let buffer = buffer.read(cx);                 tracked.version != buffer.version                    && buffer                        .file()                        .map_or(false, |file| file.disk_state() != DiskState::Deleted)            })            .map(|(buffer, _)| buffer)    }} fn apply_non_conflicting_edits(    patch: &Patch<u32>,    edits: Vec<Edit<u32>>,    old_text: &mut Rope,    new_text: &Rope,) {    let mut old_edits = patch.edits().iter().cloned().peekable();    let mut new_edits = edits.into_iter().peekable();    let mut applied_delta = 0i32;    let mut rebased_delta = 0i32;     while let Some(mut new_edit) = new_edits.next() {        let mut conflict = false;         // Push all the old edits that are before this new edit or that intersect with it.        while let Some(old_edit) = old_edits.peek() {            if new_edit.old.end < old_edit.new.start                || (!old_edit.new.is_empty() && new_edit.old.end == old_edit.new.start)            {                break;

Lines 1–500 of 1401.