From c1d6dfd832471eb428f9eafec8144ef81ff4b406 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 2 Apr 2025 10:38:26 +0200 Subject: [PATCH] Update selection when keeping/rejecting hunks (#27902) Release Notes: - N/A --- assets/keymaps/default-linux.json | 6 +- assets/keymaps/default-macos.json | 6 +- .../src/{assistant_diff.rs => agent_diff.rs} | 409 ++++++++++++++---- crates/agent/src/assistant.rs | 6 +- crates/agent/src/assistant_panel.rs | 16 +- crates/agent/src/message_editor.rs | 6 +- .../src/find_replace_file_tool.rs | 20 +- crates/multi_buffer/src/multi_buffer.rs | 6 + crates/zed/src/zed.rs | 6 +- 9 files changed, 367 insertions(+), 114 deletions(-) rename crates/agent/src/{assistant_diff.rs => agent_diff.rs} (62%) diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json index cedf508183..2d471fe407 100644 --- a/assets/keymaps/default-linux.json +++ b/assets/keymaps/default-linux.json @@ -138,7 +138,7 @@ } }, { - "context": "Editor && !assistant_diff", + "context": "Editor && !agent_diff", "bindings": { "ctrl-k ctrl-r": "git::Restore", "ctrl-alt-y": "git::ToggleStaged", @@ -147,7 +147,7 @@ } }, { - "context": "AssistantDiff", + "context": "AgentDiff", "bindings": { "ctrl-y": "agent::Keep", "ctrl-k ctrl-r": "agent::Reject" @@ -645,7 +645,7 @@ "bindings": { "enter": "agent::Chat", "ctrl-i": "agent::ToggleProfileSelector", - "shift-ctrl-r": "agent::OpenAssistantDiff" + "shift-ctrl-r": "agent::OpenAgentDiff" } }, { diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json index 83e0ef3637..29aff797c5 100644 --- a/assets/keymaps/default-macos.json +++ b/assets/keymaps/default-macos.json @@ -228,7 +228,7 @@ } }, { - "context": "Editor && !assistant_diff", + "context": "Editor && !agent_diff", "use_key_equivalents": true, "bindings": { "cmd-alt-z": "git::Restore", @@ -238,7 +238,7 @@ } }, { - "context": "AssistantDiff", + "context": "AgentDiff", "use_key_equivalents": true, "bindings": { "cmd-y": "agent::Keep", @@ -305,7 +305,7 @@ "bindings": { "enter": "agent::Chat", "cmd-i": "agent::ToggleProfileSelector", - "shift-ctrl-r": "agent::OpenAssistantDiff" + "shift-ctrl-r": "agent::OpenAgentDiff" } }, { diff --git a/crates/agent/src/assistant_diff.rs b/crates/agent/src/agent_diff.rs similarity index 62% rename from crates/agent/src/assistant_diff.rs rename to crates/agent/src/agent_diff.rs index 62485a66db..69e770edc1 100644 --- a/crates/agent/src/assistant_diff.rs +++ b/crates/agent/src/agent_diff.rs @@ -3,8 +3,9 @@ use anyhow::Result; use buffer_diff::DiffHunkStatus; use collections::HashSet; use editor::{ - Direction, Editor, EditorEvent, MultiBuffer, ToPoint, + AnchorRangeExt, Direction, Editor, EditorEvent, MultiBuffer, ToPoint, actions::{GoToHunk, GoToPreviousHunk}, + scroll::Autoscroll, }; use gpui::{ Action, AnyElement, AnyView, App, Entity, EventEmitter, FocusHandle, Focusable, SharedString, @@ -26,7 +27,7 @@ use workspace::{ searchable::SearchableItemHandle, }; -pub struct AssistantDiff { +pub struct AgentDiff { multibuffer: Entity, editor: Entity, thread: Entity, @@ -36,7 +37,7 @@ pub struct AssistantDiff { _subscriptions: Vec, } -impl AssistantDiff { +impl AgentDiff { pub fn deploy( thread: Entity, workspace: WeakEntity, @@ -45,7 +46,7 @@ impl AssistantDiff { ) -> Result<()> { let existing_diff = workspace.update(cx, |workspace, cx| { workspace - .items_of_type::(cx) + .items_of_type::(cx) .find(|diff| diff.read(cx).thread == thread) })?; if let Some(existing_diff) = existing_diff { @@ -53,10 +54,10 @@ impl AssistantDiff { workspace.activate_item(&existing_diff, true, true, window, cx); }) } else { - let assistant_diff = - cx.new(|cx| AssistantDiff::new(thread.clone(), workspace.clone(), window, cx)); + let agent_diff = + cx.new(|cx| AgentDiff::new(thread.clone(), workspace.clone(), window, cx)); workspace.update(cx, |workspace, cx| { - workspace.add_item_to_center(Box::new(assistant_diff), window, cx); + workspace.add_item_to_center(Box::new(agent_diff), window, cx); }) } } @@ -72,7 +73,7 @@ impl AssistantDiff { let project = thread.read(cx).project().clone(); let render_diff_hunk_controls = Arc::new({ - let assistant_diff = cx.entity(); + let agent_diff = cx.entity(); move |row, status: &DiffHunkStatus, hunk_range, @@ -87,7 +88,7 @@ impl AssistantDiff { hunk_range, is_created_file, line_height, - &assistant_diff, + &agent_diff, editor, window, cx, @@ -100,7 +101,7 @@ impl AssistantDiff { editor.disable_inline_diagnostics(); editor.set_expand_all_diff_hunks(cx); editor.set_render_diff_hunk_controls(render_diff_hunk_controls, cx); - editor.register_addon(AssistantDiffAddon); + editor.register_addon(AgentDiffAddon); editor }); @@ -141,6 +142,7 @@ impl AssistantDiff { 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, @@ -166,9 +168,25 @@ impl AssistantDiff { self.editor.update(cx, |editor, cx| { if was_empty { - editor.change_selections(None, window, cx, |selections| { - selections.select_ranges([0..0]) - }); + 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( + Some(Autoscroll::fit()), + window, + cx, + |selections| { + selections + .select_anchor_ranges([first_hunk_start..first_hunk_start]); + }, + ) + } } if is_excerpt_newly_added @@ -222,45 +240,32 @@ impl AssistantDiff { } } - fn keep(&mut self, _: &crate::Keep, _window: &mut Window, cx: &mut Context) { + fn keep(&mut self, _: &crate::Keep, window: &mut Window, cx: &mut Context) { let ranges = self .editor .read(cx) .selections .disjoint_anchor_ranges() .collect::>(); - - let snapshot = self.multibuffer.read(cx).snapshot(cx); - let diff_hunks_in_ranges = self - .editor - .read(cx) - .diff_hunks_in_ranges(&ranges, &snapshot) - .collect::>(); - - for hunk in diff_hunks_in_ranges { - let buffer = self.multibuffer.read(cx).buffer(hunk.buffer_id); - if let Some(buffer) = buffer { - self.thread.update(cx, |thread, cx| { - thread.keep_edits_in_range(buffer, hunk.buffer_range, cx) - }); - } - } + self.keep_edits_in_ranges(ranges, window, cx); } fn reject(&mut self, _: &crate::Reject, window: &mut Window, cx: &mut Context) { let ranges = self .editor - .update(cx, |editor, cx| editor.selections.ranges(cx)); - self.editor.update(cx, |editor, cx| { - editor.restore_hunks_in_ranges(ranges, window, cx) - }) + .read(cx) + .selections + .disjoint_anchor_ranges() + .collect::>(); + self.reject_edits_in_ranges(ranges, window, cx); } fn reject_all(&mut self, _: &crate::RejectAll, window: &mut Window, cx: &mut Context) { - self.editor.update(cx, |editor, cx| { - let max_point = editor.buffer().read(cx).read(cx).max_point(); - editor.restore_hunks_in_ranges(vec![Point::zero()..max_point], window, cx) - }) + self.reject_edits_in_ranges( + vec![editor::Anchor::min()..editor::Anchor::max()], + window, + cx, + ); } fn keep_all(&mut self, _: &crate::KeepAll, _window: &mut Window, cx: &mut Context) { @@ -270,30 +275,110 @@ impl AssistantDiff { fn keep_edits_in_ranges( &mut self, - hunk_ranges: Vec>, + ranges: Vec>, + window: &mut Window, cx: &mut Context, ) { let snapshot = self.multibuffer.read(cx).snapshot(cx); let diff_hunks_in_ranges = self .editor .read(cx) - .diff_hunks_in_ranges(&hunk_ranges, &snapshot) + .diff_hunks_in_ranges(&ranges, &snapshot) .collect::>(); + let newest_cursor = self.editor.update(cx, |editor, cx| { + editor.selections.newest::(cx).head() + }); + if diff_hunks_in_ranges.iter().any(|hunk| { + hunk.row_range + .contains(&multi_buffer::MultiBufferRow(newest_cursor.row)) + }) { + self.update_selection(&diff_hunks_in_ranges, window, cx); + } - for hunk in diff_hunks_in_ranges { + for hunk in &diff_hunks_in_ranges { let buffer = self.multibuffer.read(cx).buffer(hunk.buffer_id); if let Some(buffer) = buffer { self.thread.update(cx, |thread, cx| { - thread.keep_edits_in_range(buffer, hunk.buffer_range, cx) + thread.keep_edits_in_range(buffer, hunk.buffer_range.clone(), cx) }); } } } + + fn reject_edits_in_ranges( + &mut self, + ranges: Vec>, + window: &mut Window, + cx: &mut Context, + ) { + let snapshot = self.multibuffer.read(cx).snapshot(cx); + let diff_hunks_in_ranges = self + .editor + .read(cx) + .diff_hunks_in_ranges(&ranges, &snapshot) + .collect::>(); + let newest_cursor = self.editor.update(cx, |editor, cx| { + editor.selections.newest::(cx).head() + }); + if diff_hunks_in_ranges.iter().any(|hunk| { + hunk.row_range + .contains(&multi_buffer::MultiBufferRow(newest_cursor.row)) + }) { + self.update_selection(&diff_hunks_in_ranges, window, cx); + } + + let point_ranges = ranges + .into_iter() + .map(|range| range.to_point(&snapshot)) + .collect(); + self.editor.update(cx, |editor, cx| { + editor.restore_hunks_in_ranges(point_ranges, window, cx) + }); + } + + fn update_selection( + &mut self, + diff_hunks: &[multi_buffer::MultiBufferDiffHunk], + window: &mut Window, + cx: &mut Context, + ) { + let snapshot = self.multibuffer.read(cx).snapshot(cx); + let target_hunk = diff_hunks + .last() + .and_then(|last_kept_hunk| { + let last_kept_hunk_end = last_kept_hunk.multi_buffer_range().end; + self.editor + .read(cx) + .diff_hunks_in_ranges(&[last_kept_hunk_end..editor::Anchor::max()], &snapshot) + .skip(1) + .next() + }) + .or_else(|| { + let first_kept_hunk = diff_hunks.first()?; + let first_kept_hunk_start = first_kept_hunk.multi_buffer_range().start; + self.editor + .read(cx) + .diff_hunks_in_ranges( + &[editor::Anchor::min()..first_kept_hunk_start], + &snapshot, + ) + .next() + }); + + if let Some(target_hunk) = target_hunk { + self.editor.update(cx, |editor, cx| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| { + let next_hunk_start = target_hunk.multi_buffer_range().start; + selections.select_anchor_ranges([next_hunk_start..next_hunk_start]); + }) + }); + } + } } -impl EventEmitter for AssistantDiff {} +impl EventEmitter for AgentDiff {} -impl Focusable for AssistantDiff { +impl Focusable for AgentDiff { fn focus_handle(&self, cx: &App) -> FocusHandle { if self.multibuffer.read(cx).is_empty() { self.focus_handle.clone() @@ -303,7 +388,7 @@ impl Focusable for AssistantDiff { } } -impl Item for AssistantDiff { +impl Item for AgentDiff { type Event = EditorEvent; fn tab_icon(&self, _window: &Window, _cx: &App) -> Option { @@ -330,7 +415,7 @@ impl Item for AssistantDiff { } fn tab_tooltip_text(&self, _: &App) -> Option { - Some("Assistant Diff".into()) + Some("Agent Diff".into()) } fn tab_content(&self, params: TabContentParams, _window: &Window, cx: &App) -> AnyElement { @@ -467,17 +552,13 @@ impl Item for AssistantDiff { } } -impl Render for AssistantDiff { +impl Render for AgentDiff { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let is_empty = self.multibuffer.read(cx).is_empty(); div() .track_focus(&self.focus_handle) - .key_context(if is_empty { - "EmptyPane" - } else { - "AssistantDiff" - }) + .key_context(if is_empty { "EmptyPane" } else { "AgentDiff" }) .on_action(cx.listener(Self::keep)) .on_action(cx.listener(Self::reject)) .on_action(cx.listener(Self::reject_all)) @@ -498,7 +579,7 @@ fn render_diff_hunk_controls( hunk_range: Range, is_created_file: bool, line_height: Pixels, - assistant_diff: &Entity, + agent_diff: &Entity, editor: &Entity, window: &mut Window, cx: &mut App, @@ -519,7 +600,7 @@ fn render_diff_hunk_controls( .occlude() .shadow_md() .children(vec![ - Button::new("reject", "Reject") + Button::new(("reject", row as u64), "Reject") .disabled(is_created_file) .key_binding( KeyBinding::for_action_in( @@ -531,12 +612,14 @@ fn render_diff_hunk_controls( .map(|kb| kb.size(rems_from_px(12.))), ) .on_click({ - let editor = editor.clone(); + let agent_diff = agent_diff.clone(); move |_event, window, cx| { - editor.update(cx, |editor, cx| { - let snapshot = editor.snapshot(window, cx); - let point = hunk_range.start.to_point(&snapshot.buffer_snapshot); - editor.restore_hunks_in_ranges(vec![point..point], window, cx); + agent_diff.update(cx, |diff, cx| { + diff.reject_edits_in_ranges( + vec![hunk_range.start..hunk_range.start], + window, + cx, + ); }); } }), @@ -551,10 +634,14 @@ fn render_diff_hunk_controls( .map(|kb| kb.size(rems_from_px(12.))), ) .on_click({ - let assistant_diff = assistant_diff.clone(); - move |_event, _window, cx| { - assistant_diff.update(cx, |diff, cx| { - diff.keep_edits_in_ranges(vec![hunk_range.start..hunk_range.start], cx); + let agent_diff = agent_diff.clone(); + move |_event, window, cx| { + agent_diff.update(cx, |diff, cx| { + diff.keep_edits_in_ranges( + vec![hunk_range.start..hunk_range.start], + window, + cx, + ); }); } }), @@ -639,38 +726,38 @@ fn render_diff_hunk_controls( .into_any_element() } -struct AssistantDiffAddon; +struct AgentDiffAddon; -impl editor::Addon for AssistantDiffAddon { +impl editor::Addon for AgentDiffAddon { fn to_any(&self) -> &dyn std::any::Any { self } fn extend_key_context(&self, key_context: &mut gpui::KeyContext, _: &App) { - key_context.add("assistant_diff"); + key_context.add("agent_diff"); } } -pub struct AssistantDiffToolbar { - assistant_diff: Option>, +pub struct AgentDiffToolbar { + agent_diff: Option>, _workspace: WeakEntity, } -impl AssistantDiffToolbar { +impl AgentDiffToolbar { pub fn new(workspace: &Workspace, _: &mut Context) -> Self { Self { - assistant_diff: None, + agent_diff: None, _workspace: workspace.weak_handle(), } } - fn assistant_diff(&self, _: &App) -> Option> { - self.assistant_diff.as_ref()?.upgrade() + fn agent_diff(&self, _: &App) -> Option> { + self.agent_diff.as_ref()?.upgrade() } fn dispatch_action(&self, action: &dyn Action, window: &mut Window, cx: &mut Context) { - if let Some(assistant_diff) = self.assistant_diff(cx) { - assistant_diff.focus_handle(cx).focus(window); + if let Some(agent_diff) = self.agent_diff(cx) { + agent_diff.focus_handle(cx).focus(window); } let action = action.boxed_clone(); cx.defer(move |cx| { @@ -679,19 +766,19 @@ impl AssistantDiffToolbar { } } -impl EventEmitter for AssistantDiffToolbar {} +impl EventEmitter for AgentDiffToolbar {} -impl ToolbarItemView for AssistantDiffToolbar { +impl ToolbarItemView for AgentDiffToolbar { fn set_active_pane_item( &mut self, active_pane_item: Option<&dyn ItemHandle>, _: &mut Window, cx: &mut Context, ) -> ToolbarItemLocation { - self.assistant_diff = active_pane_item - .and_then(|item| item.act_as::(cx)) + self.agent_diff = active_pane_item + .and_then(|item| item.act_as::(cx)) .map(|entity| entity.downgrade()); - if self.assistant_diff.is_some() { + if self.agent_diff.is_some() { ToolbarItemLocation::PrimaryRight } else { ToolbarItemLocation::Hidden @@ -707,14 +794,14 @@ impl ToolbarItemView for AssistantDiffToolbar { } } -impl Render for AssistantDiffToolbar { +impl Render for AgentDiffToolbar { fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { - let assistant_diff = match self.assistant_diff(cx) { + let agent_diff = match self.agent_diff(cx) { Some(ad) => ad, None => return div(), }; - let is_empty = assistant_diff.read(cx).multibuffer.read(cx).is_empty(); + let is_empty = agent_diff.read(cx).multibuffer.read(cx).is_empty(); if is_empty { return div(); @@ -741,3 +828,161 @@ impl Render for AssistantDiffToolbar { ) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ThreadStore, thread_store}; + use assistant_settings::AssistantSettings; + use context_server::ContextServerSettings; + use editor::EditorSettings; + use gpui::TestAppContext; + use project::{FakeFs, Project}; + use prompt_store::PromptBuilder; + use serde_json::json; + use settings::{Settings, SettingsStore}; + use std::sync::Arc; + use theme::ThemeSettings; + use util::path; + + #[gpui::test] + async fn test_agent_diff(cx: &mut TestAppContext) { + cx.update(|cx| { + let settings_store = SettingsStore::test(cx); + cx.set_global(settings_store); + language::init(cx); + Project::init_settings(cx); + AssistantSettings::register(cx); + thread_store::init(cx); + workspace::init_settings(cx); + ThemeSettings::register(cx); + ContextServerSettings::register(cx); + EditorSettings::register(cx); + }); + + let fs = FakeFs::new(cx.executor()); + fs.insert_tree( + path!("/test"), + json!({"file1": "abc\ndef\nghi\njkl\nmno\npqr\nstu\nvwx\nyz"}), + ) + .await; + let project = Project::test(fs, [path!("/test").as_ref()], cx).await; + let buffer_path = project + .read_with(cx, |project, cx| { + project.find_project_path("test/file1", cx) + }) + .unwrap(); + + let thread_store = cx.update(|cx| { + ThreadStore::new( + project.clone(), + Arc::default(), + Arc::new(PromptBuilder::new(None).unwrap()), + cx, + ) + .unwrap() + }); + let thread = thread_store.update(cx, |store, cx| store.create_thread(cx)); + let action_log = thread.read_with(cx, |thread, _| thread.action_log().clone()); + + let (workspace, cx) = + cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx)); + let agent_diff = cx.new_window_entity(|window, cx| { + AgentDiff::new(thread.clone(), workspace.downgrade(), window, cx) + }); + let editor = agent_diff.read_with(cx, |diff, _cx| diff.editor.clone()); + + let buffer = project + .update(cx, |project, cx| project.open_buffer(buffer_path, cx)) + .await + .unwrap(); + cx.update(|_, cx| { + action_log.update(cx, |log, cx| log.buffer_read(buffer.clone(), cx)); + buffer.update(cx, |buffer, cx| { + buffer + .edit( + [ + (Point::new(1, 1)..Point::new(1, 2), "E"), + (Point::new(3, 2)..Point::new(3, 3), "L"), + (Point::new(5, 0)..Point::new(5, 1), "P"), + (Point::new(7, 1)..Point::new(7, 2), "W"), + ], + None, + cx, + ) + .unwrap() + }); + action_log.update(cx, |log, cx| log.buffer_edited(buffer.clone(), cx)); + }); + cx.run_until_parked(); + + // When opening the assistant diff, the cursor is positioned on the first hunk. + assert_eq!( + editor.read_with(cx, |editor, cx| editor.text(cx)), + "abc\ndef\ndEf\nghi\njkl\njkL\nmno\npqr\nPqr\nstu\nvwx\nvWx\nyz" + ); + assert_eq!( + editor + .update(cx, |editor, cx| editor.selections.newest::(cx)) + .range(), + Point::new(1, 0)..Point::new(1, 0) + ); + + // After keeping a hunk, the cursor should be positioned on the second hunk. + agent_diff.update_in(cx, |diff, window, cx| diff.keep(&crate::Keep, window, cx)); + cx.run_until_parked(); + assert_eq!( + editor.read_with(cx, |editor, cx| editor.text(cx)), + "abc\ndEf\nghi\njkl\njkL\nmno\npqr\nPqr\nstu\nvwx\nvWx\nyz" + ); + assert_eq!( + editor + .update(cx, |editor, cx| editor.selections.newest::(cx)) + .range(), + Point::new(3, 0)..Point::new(3, 0) + ); + + // Restoring a hunk also moves the cursor to the next hunk, possibly cycling if it's at the end. + editor.update_in(cx, |editor, window, cx| { + editor.change_selections(None, window, cx, |selections| { + selections.select_ranges([Point::new(10, 0)..Point::new(10, 0)]) + }); + }); + agent_diff.update_in(cx, |diff, window, cx| { + diff.reject(&crate::Reject, window, cx) + }); + cx.run_until_parked(); + assert_eq!( + editor.read_with(cx, |editor, cx| editor.text(cx)), + "abc\ndEf\nghi\njkl\njkL\nmno\npqr\nPqr\nstu\nvwx\nyz" + ); + assert_eq!( + editor + .update(cx, |editor, cx| editor.selections.newest::(cx)) + .range(), + Point::new(3, 0)..Point::new(3, 0) + ); + + // Keeping a range that doesn't intersect the current selection doesn't move it. + agent_diff.update_in(cx, |diff, window, cx| { + let position = editor + .read(cx) + .buffer() + .read(cx) + .read(cx) + .anchor_before(Point::new(7, 0)); + diff.keep_edits_in_ranges(vec![position..position], window, cx) + }); + cx.run_until_parked(); + assert_eq!( + editor.read_with(cx, |editor, cx| editor.text(cx)), + "abc\ndEf\nghi\njkl\njkL\nmno\nPqr\nstu\nvwx\nyz" + ); + assert_eq!( + editor + .update(cx, |editor, cx| editor.selections.newest::(cx)) + .range(), + Point::new(3, 0)..Point::new(3, 0) + ); + } +} diff --git a/crates/agent/src/assistant.rs b/crates/agent/src/assistant.rs index 8f1993442f..e9fa9f5e2d 100644 --- a/crates/agent/src/assistant.rs +++ b/crates/agent/src/assistant.rs @@ -1,6 +1,6 @@ mod active_thread; +mod agent_diff; mod assistant_configuration; -mod assistant_diff; mod assistant_model_selector; mod assistant_panel; mod buffer_codegen; @@ -41,7 +41,7 @@ pub use crate::assistant_panel::{AssistantPanel, ConcreteAssistantPanelDelegate} pub use crate::inline_assistant::InlineAssistant; pub use crate::thread::{Message, RequestKind, Thread, ThreadEvent}; pub use crate::thread_store::ThreadStore; -pub use assistant_diff::{AssistantDiff, AssistantDiffToolbar}; +pub use agent_diff::{AgentDiff, AgentDiffToolbar}; actions!( agent, @@ -65,7 +65,7 @@ actions!( RemoveFocusedContext, AcceptSuggestedContext, OpenActiveThreadAsMarkdown, - OpenAssistantDiff, + OpenAgentDiff, Keep, Reject, RejectAll, diff --git a/crates/agent/src/assistant_panel.rs b/crates/agent/src/assistant_panel.rs index 70d5176902..1f01f06965 100644 --- a/crates/agent/src/assistant_panel.rs +++ b/crates/agent/src/assistant_panel.rs @@ -42,8 +42,8 @@ use crate::thread::{Thread, ThreadError, ThreadId}; use crate::thread_history::{PastContext, PastThread, ThreadHistory}; use crate::thread_store::ThreadStore; use crate::{ - AssistantDiff, InlineAssistant, NewPromptEditor, NewThread, OpenActiveThreadAsMarkdown, - OpenAssistantDiff, OpenConfiguration, OpenHistory, ToggleContextPicker, + AgentDiff, InlineAssistant, NewPromptEditor, NewThread, OpenActiveThreadAsMarkdown, + OpenAgentDiff, OpenConfiguration, OpenHistory, ToggleContextPicker, }; action_with_deprecated_aliases!( @@ -94,11 +94,11 @@ pub fn init(cx: &mut App) { panel.update(cx, |panel, cx| panel.open_configuration(window, cx)); } }) - .register_action(|workspace, _: &OpenAssistantDiff, window, cx| { + .register_action(|workspace, _: &OpenAgentDiff, window, cx| { if let Some(panel) = workspace.panel::(cx) { workspace.focus_panel::(window, cx); panel.update(cx, |panel, cx| { - panel.open_assistant_diff(&OpenAssistantDiff, window, cx); + panel.open_agent_diff(&OpenAgentDiff, window, cx); }); } }); @@ -475,14 +475,14 @@ impl AssistantPanel { }) } - pub fn open_assistant_diff( + pub fn open_agent_diff( &mut self, - _: &OpenAssistantDiff, + _: &OpenAgentDiff, window: &mut Window, cx: &mut Context, ) { let thread = self.thread.read(cx).thread().clone(); - AssistantDiff::deploy(thread, self.workspace.clone(), window, cx).log_err(); + AgentDiff::deploy(thread, self.workspace.clone(), window, cx).log_err(); } pub(crate) fn open_configuration(&mut self, window: &mut Window, cx: &mut Context) { @@ -1330,7 +1330,7 @@ impl Render for AssistantPanel { })) .on_action(cx.listener(Self::open_active_thread_as_markdown)) .on_action(cx.listener(Self::deploy_prompt_library)) - .on_action(cx.listener(Self::open_assistant_diff)) + .on_action(cx.listener(Self::open_agent_diff)) .child(self.render_toolbar(window, cx)) .map(|parent| match self.active_view { ActiveView::Thread => parent diff --git a/crates/agent/src/message_editor.rs b/crates/agent/src/message_editor.rs index b1733edd3f..48a39a86b3 100644 --- a/crates/agent/src/message_editor.rs +++ b/crates/agent/src/message_editor.rs @@ -31,7 +31,7 @@ use crate::profile_selector::ProfileSelector; use crate::thread::{RequestKind, Thread}; use crate::thread_store::ThreadStore; use crate::{ - AssistantDiff, Chat, ChatMode, NewThread, OpenAssistantDiff, RemoveAllContext, ThreadEvent, + AgentDiff, Chat, ChatMode, NewThread, OpenAgentDiff, RemoveAllContext, ThreadEvent, ToggleContextPicker, ToggleProfileSelector, }; @@ -318,7 +318,7 @@ impl MessageEditor { } fn handle_review_click(&self, window: &mut Window, cx: &mut Context) { - AssistantDiff::deploy(self.thread.clone(), self.workspace.clone(), window, cx).log_err(); + AgentDiff::deploy(self.thread.clone(), self.workspace.clone(), window, cx).log_err(); } } @@ -527,7 +527,7 @@ impl Render for MessageEditor { .label_size(LabelSize::Small) .key_binding( KeyBinding::for_action_in( - &OpenAssistantDiff, + &OpenAgentDiff, &focus_handle, window, cx, diff --git a/crates/assistant_tools/src/find_replace_file_tool.rs b/crates/assistant_tools/src/find_replace_file_tool.rs index fb05170bad..5cd3faa150 100644 --- a/crates/assistant_tools/src/find_replace_file_tool.rs +++ b/crates/assistant_tools/src/find_replace_file_tool.rs @@ -225,15 +225,17 @@ impl Tool for FindReplaceFileTool { return Err(err) }; - let snapshot = buffer.update(cx, |buffer, cx| { - buffer.finalize_last_transaction(); - buffer.apply_diff(diff, cx); - buffer.finalize_last_transaction(); - buffer.snapshot() - })?; - - action_log.update(cx, |log, cx| { - log.buffer_edited(buffer.clone(), cx) + let snapshot = cx.update(|cx| { + let snapshot = buffer.update(cx, |buffer, cx| { + buffer.finalize_last_transaction(); + buffer.apply_diff(diff, cx); + buffer.finalize_last_transaction(); + buffer.snapshot() + }); + action_log.update(cx, |log, cx| { + log.buffer_edited(buffer.clone(), cx) + }); + snapshot })?; project.update( cx, |project, cx| { diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index ccd454ef13..49ea962341 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -163,6 +163,12 @@ impl MultiBufferDiffHunk { self.diff_base_byte_range == (0..0) && self.buffer_range == (text::Anchor::MIN..text::Anchor::MAX) } + + pub fn multi_buffer_range(&self) -> Range { + let start = Anchor::in_buffer(self.excerpt_id, self.buffer_id, self.buffer_range.start); + let end = Anchor::in_buffer(self.excerpt_id, self.buffer_id, self.buffer_range.end); + start..end + } } #[derive(PartialEq, Eq, Ord, PartialOrd, Clone, Hash, Debug)] diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 16ee607ddc..4c3a24100d 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -8,7 +8,7 @@ mod quick_action_bar; #[cfg(target_os = "windows")] pub(crate) mod windows_only_instance; -use agent::AssistantDiffToolbar; +use agent::AgentDiffToolbar; use anyhow::Context as _; pub use app_menus::*; use assets::Assets; @@ -938,8 +938,8 @@ fn initialize_pane( toolbar.add_item(migration_banner, window, cx); let project_diff_toolbar = cx.new(|cx| ProjectDiffToolbar::new(workspace, cx)); toolbar.add_item(project_diff_toolbar, window, cx); - let assistant_diff_toolbar = cx.new(|cx| AssistantDiffToolbar::new(workspace, cx)); - toolbar.add_item(assistant_diff_toolbar, window, cx); + let agent_diff_toolbar = cx.new(|cx| AgentDiffToolbar::new(workspace, cx)); + toolbar.add_item(agent_diff_toolbar, window, cx); }) }); }