use crate::{Keep, KeepAll, OpenAgentDiff, Reject, RejectAll};use acp::{AcpThread, AcpThreadEvent};use agent::{Thread, ThreadEvent, ThreadSummary};use agent_settings::AgentSettings;use anyhow::Result;use assistant_tool::ActionLog;use buffer_diff::DiffHunkStatus;use collections::{HashMap, HashSet};use editor::{ Direction, Editor, EditorEvent, EditorSettings, MultiBuffer, MultiBufferSnapshot, SelectionEffects, ToPoint, actions::{GoToHunk, GoToPreviousHunk}, scroll::Autoscroll,};use gpui::{ Action, Animation, AnimationExt, AnyElement, AnyView, App, AppContext, Empty, Entity, EventEmitter, FocusHandle, Focusable, Global, SharedString, Subscription, Task, Transformation, WeakEntity, Window, percentage, 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, time::Duration,};use ui::{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::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).clone(); 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).clone(); let mut this = Self { _subscriptions: [ Some( cx.observe_in(&action_log, window, |this, _action_log, window, cx| { this.update_excerpts(window, cx) }), ), match &thread { AgentDiffThread::Native(thread) => { Some(cx.subscribe(&thread, |this, _thread, event, cx| { this.handle_thread_event(event, cx) })) } AgentDiffThread::AcpThread(_) => None, }, ] .into_iter() .flatten() .collect(), 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, editor::DEFAULT_MULTIBUFFER_CONTEXT, 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() .map_or(false, |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_thread_event(&mut self, event: &ThreadEvent, cx: &mut Context<Self>) { match event { ThreadEvent::SummaryGenerated => 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| { let last_kept_hunk_end = last_kept_hunk.multi_buffer_range().end;Lines 1–500 of 2377. Load more
use crate::{Keep, KeepAll, OpenAgentDiff, Reject, RejectAll}; use acp::{AcpThread, AcpThreadEvent}; use agent::{Thread, ThreadEvent, ThreadSummary}; use agent_settings::AgentSettings; use anyhow::Result; use assistant_tool::ActionLog; use buffer_diff::DiffHunkStatus; use collections::{HashMap, HashSet}; use editor::{ Direction, Editor, EditorEvent, EditorSettings, MultiBuffer, MultiBufferSnapshot, SelectionEffects, ToPoint, actions::{GoToHunk, GoToPreviousHunk}, scroll::Autoscroll, }; use gpui::{ Action, Animation, AnimationExt, AnyElement, AnyView, App, AppContext, Empty, Entity, EventEmitter, FocusHandle, Focusable, Global, SharedString, Subscription, Task, Transformation, WeakEntity, Window, percentage, 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, time::Duration, }; use ui::{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::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).clone(); 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).clone(); let mut this = Self { _subscriptions: [ Some( cx.observe_in(&action_log, window, |this, _action_log, window, cx| { this.update_excerpts(window, cx) }), ), match &thread { AgentDiffThread::Native(thread) => { Some(cx.subscribe(&thread, |this, _thread, event, cx| { this.handle_thread_event(event, cx) })) } AgentDiffThread::AcpThread(_) => None, }, ] .into_iter() .flatten() .collect(), 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, editor::DEFAULT_MULTIBUFFER_CONTEXT, 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() .map_or(false, |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_thread_event(&mut self, event: &ThreadEvent, cx: &mut Context<Self>) { match event { ThreadEvent::SummaryGenerated => 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| { let last_kept_hunk_end = last_kept_hunk.multi_buffer_range().end;