2373 lines · 82.8 KB · Rust
RawDownload
use crate::{Keep, KeepAll, OpenAgentDiff, Reject, RejectAll};use acp_thread::{AcpThread, AcpThreadEvent};use action_log::ActionLog;use agent::{Thread, ThreadEvent, ThreadSummary};use agent_settings::AgentSettings;use anyhow::Result;use buffer_diff::DiffHunkStatus;use collections::{HashMap, HashSet};use editor::{    Direction, Editor, EditorEvent, EditorSettings, MultiBuffer, MultiBufferSnapshot,    SelectionEffects, ToPoint,    actions::{GoToHunk, GoToPreviousHunk},    multibuffer_context_lines,    scroll::Autoscroll,};use gpui::{    Action, AnyElement, AnyView, App, AppContext, Empty, Entity, EventEmitter, FocusHandle,    Focusable, Global, SharedString, Subscription, Task, WeakEntity, Window, prelude::*,}; use language::{Buffer, Capability, DiskState, OffsetRangeExt, Point};use language_model::StopReason;use multi_buffer::PathKey;use project::{Project, ProjectItem, ProjectPath};use settings::{Settings, SettingsStore};use std::{    any::{Any, TypeId},    collections::hash_map::Entry,    ops::Range,    sync::Arc,};use ui::{CommonAnimationExt, IconButtonShape, KeyBinding, Tooltip, prelude::*, vertical_divider};use util::ResultExt;use workspace::{    Item, ItemHandle, ItemNavHistory, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView,    Workspace,    item::{BreadcrumbText, ItemEvent, SaveOptions, TabContentParams},    searchable::SearchableItemHandle,};use zed_actions::assistant::ToggleFocus; pub struct AgentDiffPane {    multibuffer: Entity<MultiBuffer>,    editor: Entity<Editor>,    thread: AgentDiffThread,    focus_handle: FocusHandle,    workspace: WeakEntity<Workspace>,    title: SharedString,    _subscriptions: Vec<Subscription>,} #[derive(PartialEq, Eq, Clone)]pub enum AgentDiffThread {    Native(Entity<Thread>),    AcpThread(Entity<AcpThread>),} impl AgentDiffThread {    fn project(&self, cx: &App) -> Entity<Project> {        match self {            AgentDiffThread::Native(thread) => thread.read(cx).project().clone(),            AgentDiffThread::AcpThread(thread) => thread.read(cx).project().clone(),        }    }    fn action_log(&self, cx: &App) -> Entity<ActionLog> {        match self {            AgentDiffThread::Native(thread) => thread.read(cx).action_log().clone(),            AgentDiffThread::AcpThread(thread) => thread.read(cx).action_log().clone(),        }    }     fn summary(&self, cx: &App) -> ThreadSummary {        match self {            AgentDiffThread::Native(thread) => thread.read(cx).summary().clone(),            AgentDiffThread::AcpThread(thread) => ThreadSummary::Ready(thread.read(cx).title()),        }    }     fn is_generating(&self, cx: &App) -> bool {        match self {            AgentDiffThread::Native(thread) => thread.read(cx).is_generating(),            AgentDiffThread::AcpThread(thread) => {                thread.read(cx).status() == acp_thread::ThreadStatus::Generating            }        }    }     fn has_pending_edit_tool_uses(&self, cx: &App) -> bool {        match self {            AgentDiffThread::Native(thread) => thread.read(cx).has_pending_edit_tool_uses(),            AgentDiffThread::AcpThread(thread) => thread.read(cx).has_pending_edit_tool_calls(),        }    }     fn downgrade(&self) -> WeakAgentDiffThread {        match self {            AgentDiffThread::Native(thread) => WeakAgentDiffThread::Native(thread.downgrade()),            AgentDiffThread::AcpThread(thread) => {                WeakAgentDiffThread::AcpThread(thread.downgrade())            }        }    }} impl From<Entity<Thread>> for AgentDiffThread {    fn from(entity: Entity<Thread>) -> Self {        AgentDiffThread::Native(entity)    }} impl From<Entity<AcpThread>> for AgentDiffThread {    fn from(entity: Entity<AcpThread>) -> Self {        AgentDiffThread::AcpThread(entity)    }} #[derive(PartialEq, Eq, Clone)]pub enum WeakAgentDiffThread {    Native(WeakEntity<Thread>),    AcpThread(WeakEntity<AcpThread>),} impl WeakAgentDiffThread {    pub fn upgrade(&self) -> Option<AgentDiffThread> {        match self {            WeakAgentDiffThread::Native(weak) => weak.upgrade().map(AgentDiffThread::Native),            WeakAgentDiffThread::AcpThread(weak) => weak.upgrade().map(AgentDiffThread::AcpThread),        }    }} impl From<WeakEntity<Thread>> for WeakAgentDiffThread {    fn from(entity: WeakEntity<Thread>) -> Self {        WeakAgentDiffThread::Native(entity)    }} impl From<WeakEntity<AcpThread>> for WeakAgentDiffThread {    fn from(entity: WeakEntity<AcpThread>) -> Self {        WeakAgentDiffThread::AcpThread(entity)    }} impl AgentDiffPane {    pub fn deploy(        thread: impl Into<AgentDiffThread>,        workspace: WeakEntity<Workspace>,        window: &mut Window,        cx: &mut App,    ) -> Result<Entity<Self>> {        workspace.update(cx, |workspace, cx| {            Self::deploy_in_workspace(thread, workspace, window, cx)        })    }     pub fn deploy_in_workspace(        thread: impl Into<AgentDiffThread>,        workspace: &mut Workspace,        window: &mut Window,        cx: &mut Context<Workspace>,    ) -> Entity<Self> {        let thread = thread.into();        let existing_diff = workspace            .items_of_type::<AgentDiffPane>(cx)            .find(|diff| diff.read(cx).thread == thread);         if let Some(existing_diff) = existing_diff {            workspace.activate_item(&existing_diff, true, true, window, cx);            existing_diff        } else {            let agent_diff = cx                .new(|cx| AgentDiffPane::new(thread.clone(), workspace.weak_handle(), window, cx));            workspace.add_item_to_center(Box::new(agent_diff.clone()), window, cx);            agent_diff        }    }     pub fn new(        thread: AgentDiffThread,        workspace: WeakEntity<Workspace>,        window: &mut Window,        cx: &mut Context<Self>,    ) -> Self {        let focus_handle = cx.focus_handle();        let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));         let project = thread.project(cx);        let editor = cx.new(|cx| {            let mut editor =                Editor::for_multibuffer(multibuffer.clone(), Some(project.clone()), window, cx);            editor.disable_inline_diagnostics();            editor.set_expand_all_diff_hunks(cx);            editor.set_render_diff_hunk_controls(diff_hunk_controls(&thread), cx);            editor.register_addon(AgentDiffAddon);            editor        });         let action_log = thread.action_log(cx);         let mut this = Self {            _subscriptions: vec![                cx.observe_in(&action_log, window, |this, _action_log, window, cx| {                    this.update_excerpts(window, cx)                }),                match &thread {                    AgentDiffThread::Native(thread) => cx                        .subscribe(thread, |this, _thread, event, cx| {                            this.handle_native_thread_event(event, cx)                        }),                    AgentDiffThread::AcpThread(thread) => cx                        .subscribe(thread, |this, _thread, event, cx| {                            this.handle_acp_thread_event(event, cx)                        }),                },            ],            title: SharedString::default(),            multibuffer,            editor,            thread,            focus_handle,            workspace,        };        this.update_excerpts(window, cx);        this.update_title(cx);        this    }     fn update_excerpts(&mut self, window: &mut Window, cx: &mut Context<Self>) {        let changed_buffers = self.thread.action_log(cx).read(cx).changed_buffers(cx);        let mut paths_to_delete = self.multibuffer.read(cx).paths().collect::<HashSet<_>>();         for (buffer, diff_handle) in changed_buffers {            if buffer.read(cx).file().is_none() {                continue;            }             let path_key = PathKey::for_buffer(&buffer, cx);            paths_to_delete.remove(&path_key);             let snapshot = buffer.read(cx).snapshot();            let diff = diff_handle.read(cx);             let diff_hunk_ranges = diff                .hunks_intersecting_range(                    language::Anchor::MIN..language::Anchor::MAX,                    &snapshot,                    cx,                )                .map(|diff_hunk| diff_hunk.buffer_range.to_point(&snapshot))                .collect::<Vec<_>>();             let (was_empty, is_excerpt_newly_added) =                self.multibuffer.update(cx, |multibuffer, cx| {                    let was_empty = multibuffer.is_empty();                    let (_, is_excerpt_newly_added) = multibuffer.set_excerpts_for_path(                        path_key.clone(),                        buffer.clone(),                        diff_hunk_ranges,                        multibuffer_context_lines(cx),                        cx,                    );                    multibuffer.add_diff(diff_handle, cx);                    (was_empty, is_excerpt_newly_added)                });             self.editor.update(cx, |editor, cx| {                if was_empty {                    let first_hunk = editor                        .diff_hunks_in_ranges(                            &[editor::Anchor::min()..editor::Anchor::max()],                            &self.multibuffer.read(cx).read(cx),                        )                        .next();                     if let Some(first_hunk) = first_hunk {                        let first_hunk_start = first_hunk.multi_buffer_range().start;                        editor.change_selections(Default::default(), window, cx, |selections| {                            selections.select_anchor_ranges([first_hunk_start..first_hunk_start]);                        })                    }                }                 if is_excerpt_newly_added                    && buffer                        .read(cx)                        .file()                        .is_some_and(|file| file.disk_state() == DiskState::Deleted)                {                    editor.fold_buffer(snapshot.text.remote_id(), cx)                }            });        }         self.multibuffer.update(cx, |multibuffer, cx| {            for path in paths_to_delete {                multibuffer.remove_excerpts_for_path(path, cx);            }        });         if self.multibuffer.read(cx).is_empty()            && self                .editor                .read(cx)                .focus_handle(cx)                .contains_focused(window, cx)        {            self.focus_handle.focus(window);        } else if self.focus_handle.is_focused(window) && !self.multibuffer.read(cx).is_empty() {            self.editor.update(cx, |editor, cx| {                editor.focus_handle(cx).focus(window);            });        }    }     fn update_title(&mut self, cx: &mut Context<Self>) {        let new_title = self.thread.summary(cx).unwrap_or("Agent Changes");        if new_title != self.title {            self.title = new_title;            cx.emit(EditorEvent::TitleChanged);        }    }     fn handle_native_thread_event(&mut self, event: &ThreadEvent, cx: &mut Context<Self>) {        if let ThreadEvent::SummaryGenerated = event {            self.update_title(cx)        }    }     fn handle_acp_thread_event(&mut self, event: &AcpThreadEvent, cx: &mut Context<Self>) {        if let AcpThreadEvent::TitleUpdated = event {            self.update_title(cx)        }    }     pub fn move_to_path(&self, path_key: PathKey, window: &mut Window, cx: &mut App) {        if let Some(position) = self.multibuffer.read(cx).location_for_path(&path_key, cx) {            self.editor.update(cx, |editor, cx| {                let first_hunk = editor                    .diff_hunks_in_ranges(                        &[position..editor::Anchor::max()],                        &self.multibuffer.read(cx).read(cx),                    )                    .next();                 if let Some(first_hunk) = first_hunk {                    let first_hunk_start = first_hunk.multi_buffer_range().start;                    editor.change_selections(Default::default(), window, cx, |selections| {                        selections.select_anchor_ranges([first_hunk_start..first_hunk_start]);                    })                }            });        }    }     fn keep(&mut self, _: &Keep, window: &mut Window, cx: &mut Context<Self>) {        self.editor.update(cx, |editor, cx| {            let snapshot = editor.buffer().read(cx).snapshot(cx);            keep_edits_in_selection(editor, &snapshot, &self.thread, window, cx);        });    }     fn reject(&mut self, _: &Reject, window: &mut Window, cx: &mut Context<Self>) {        self.editor.update(cx, |editor, cx| {            let snapshot = editor.buffer().read(cx).snapshot(cx);            reject_edits_in_selection(editor, &snapshot, &self.thread, window, cx);        });    }     fn reject_all(&mut self, _: &RejectAll, window: &mut Window, cx: &mut Context<Self>) {        self.editor.update(cx, |editor, cx| {            let snapshot = editor.buffer().read(cx).snapshot(cx);            reject_edits_in_ranges(                editor,                &snapshot,                &self.thread,                vec![editor::Anchor::min()..editor::Anchor::max()],                window,                cx,            );        });    }     fn keep_all(&mut self, _: &KeepAll, _window: &mut Window, cx: &mut Context<Self>) {        self.thread            .action_log(cx)            .update(cx, |action_log, cx| action_log.keep_all_edits(cx))    }} fn keep_edits_in_selection(    editor: &mut Editor,    buffer_snapshot: &MultiBufferSnapshot,    thread: &AgentDiffThread,    window: &mut Window,    cx: &mut Context<Editor>,) {    let ranges = editor        .selections        .disjoint_anchor_ranges()        .collect::<Vec<_>>();     keep_edits_in_ranges(editor, buffer_snapshot, thread, ranges, window, cx)} fn reject_edits_in_selection(    editor: &mut Editor,    buffer_snapshot: &MultiBufferSnapshot,    thread: &AgentDiffThread,    window: &mut Window,    cx: &mut Context<Editor>,) {    let ranges = editor        .selections        .disjoint_anchor_ranges()        .collect::<Vec<_>>();    reject_edits_in_ranges(editor, buffer_snapshot, thread, ranges, window, cx)} fn keep_edits_in_ranges(    editor: &mut Editor,    buffer_snapshot: &MultiBufferSnapshot,    thread: &AgentDiffThread,    ranges: Vec<Range<editor::Anchor>>,    window: &mut Window,    cx: &mut Context<Editor>,) {    let diff_hunks_in_ranges = editor        .diff_hunks_in_ranges(&ranges, buffer_snapshot)        .collect::<Vec<_>>();     update_editor_selection(editor, buffer_snapshot, &diff_hunks_in_ranges, window, cx);     let multibuffer = editor.buffer().clone();    for hunk in &diff_hunks_in_ranges {        let buffer = multibuffer.read(cx).buffer(hunk.buffer_id);        if let Some(buffer) = buffer {            thread.action_log(cx).update(cx, |action_log, cx| {                action_log.keep_edits_in_range(buffer, hunk.buffer_range.clone(), cx)            });        }    }} fn reject_edits_in_ranges(    editor: &mut Editor,    buffer_snapshot: &MultiBufferSnapshot,    thread: &AgentDiffThread,    ranges: Vec<Range<editor::Anchor>>,    window: &mut Window,    cx: &mut Context<Editor>,) {    let diff_hunks_in_ranges = editor        .diff_hunks_in_ranges(&ranges, buffer_snapshot)        .collect::<Vec<_>>();     update_editor_selection(editor, buffer_snapshot, &diff_hunks_in_ranges, window, cx);     let multibuffer = editor.buffer().clone();     let mut ranges_by_buffer = HashMap::default();    for hunk in &diff_hunks_in_ranges {        let buffer = multibuffer.read(cx).buffer(hunk.buffer_id);        if let Some(buffer) = buffer {            ranges_by_buffer                .entry(buffer.clone())                .or_insert_with(Vec::new)                .push(hunk.buffer_range.clone());        }    }     for (buffer, ranges) in ranges_by_buffer {        thread            .action_log(cx)            .update(cx, |action_log, cx| {                action_log.reject_edits_in_ranges(buffer, ranges, cx)            })            .detach_and_log_err(cx);    }} fn update_editor_selection(    editor: &mut Editor,    buffer_snapshot: &MultiBufferSnapshot,    diff_hunks: &[multi_buffer::MultiBufferDiffHunk],    window: &mut Window,    cx: &mut Context<Editor>,) {    let newest_cursor = editor.selections.newest::<Point>(cx).head();     if !diff_hunks.iter().any(|hunk| {        hunk.row_range            .contains(&multi_buffer::MultiBufferRow(newest_cursor.row))    }) {        return;    }     let target_hunk = {        diff_hunks            .last()            .and_then(|last_kept_hunk| {

Lines 1–500 of 2373.