agent_ui: Remove Keep/Reject buttons (#46456)

Michael Benfield created

Release Notes:

- N/A

Change summary

Cargo.lock                             |   1 
crates/agent_ui/Cargo.toml             |   1 
crates/agent_ui/src/acp/thread_view.rs | 148 ++++++---------------------
crates/git_ui/src/project_diff.rs      |  56 ++++++++++
4 files changed, 91 insertions(+), 115 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -362,6 +362,7 @@ dependencies = [
  "fs",
  "futures 0.3.31",
  "fuzzy",
+ "git_ui",
  "gpui",
  "gpui_tokio",
  "html_to_markdown",

crates/agent_ui/Cargo.toml 🔗

@@ -47,6 +47,7 @@ feature_flags.workspace = true
 file_icons.workspace = true
 fs.workspace = true
 futures.workspace = true
+git_ui.workspace = true
 fuzzy.workspace = true
 gpui.workspace = true
 gpui_tokio.workspace = true

crates/agent_ui/src/acp/thread_view.rs 🔗

@@ -4,7 +4,7 @@ use acp_thread::{
     ToolCallStatus, UserMessageId,
 };
 use acp_thread::{AgentConnection, Plan};
-use action_log::{ActionLog, ActionLogTelemetry};
+use action_log::ActionLogTelemetry;
 use agent::{DbThreadMetadata, NativeAgentServer, SharedThread, ThreadStore};
 use agent_client_protocol::{self as acp, PromptCapabilities};
 use agent_servers::{AgentServer, AgentServerDelegate};
@@ -45,7 +45,7 @@ use std::sync::Arc;
 use std::time::Instant;
 use std::{collections::BTreeMap, rc::Rc, time::Duration};
 use terminal_view::terminal_panel::TerminalPanel;
-use text::{Anchor, ToPoint as _};
+use text::ToPoint as _;
 use theme::{AgentFontSize, ThemeSettings};
 use ui::{
     Callout, CommonAnimationExt, ContextMenu, ContextMenuEntry, CopyButton, DiffStat, Disclosure,
@@ -71,8 +71,8 @@ use crate::ui::{AgentNotification, AgentNotificationEvent, BurnModeTooltip, Usag
 use crate::{
     AgentDiffPane, AgentPanel, AllowAlways, AllowOnce, ClearMessageQueue, ContinueThread,
     ContinueWithBurnMode, CycleFavoriteModels, CycleModeSelector, ExpandMessageEditor, Follow,
-    KeepAll, NewThread, OpenAgentDiff, OpenHistory, QueueMessage, RejectAll, RejectOnce,
-    SendNextQueuedMessage, ToggleBurnMode, ToggleProfileSelector,
+    KeepAll, NewThread, OpenHistory, QueueMessage, RejectAll, RejectOnce, SendNextQueuedMessage,
+    ToggleBurnMode, ToggleProfileSelector,
 };
 
 const STOPWATCH_THRESHOLD: Duration = Duration::from_secs(1);
@@ -4341,7 +4341,6 @@ impl AcpThreadView {
     ) -> Option<AnyElement> {
         let thread = thread_entity.read(cx);
         let action_log = thread.action_log();
-        let telemetry = ActionLogTelemetry::from(thread);
         let changed_buffers = action_log.read(cx).changed_buffers(cx);
         let plan = thread.plan();
 
@@ -4387,13 +4386,7 @@ impl AcpThreadView {
                     cx,
                 ))
                 .when(self.edits_expanded, |parent| {
-                    parent.child(self.render_edited_files(
-                        action_log,
-                        telemetry,
-                        &changed_buffers,
-                        pending_edits,
-                        cx,
-                    ))
+                    parent.child(self.render_edited_files(&changed_buffers, cx))
                 })
             })
             .when(!self.message_queue.is_empty(), |this| {
@@ -4571,8 +4564,6 @@ impl AcpThreadView {
         pending_edits: bool,
         cx: &Context<Self>,
     ) -> Div {
-        const EDIT_NOT_READY_TOOLTIP_LABEL: &str = "Wait until file edits are complete.";
-
         let focus_handle = self.focus_handle(cx);
 
         h_flex()
@@ -4651,66 +4642,21 @@ impl AcpThreadView {
                     })),
             )
             .child(
-                h_flex()
-                    .gap_1()
-                    .child(
-                        IconButton::new("review-changes", IconName::ListTodo)
-                            .icon_size(IconSize::Small)
-                            .tooltip({
-                                let focus_handle = focus_handle.clone();
-                                move |_window, cx| {
-                                    Tooltip::for_action_in(
-                                        "Review Changes",
-                                        &OpenAgentDiff,
-                                        &focus_handle,
-                                        cx,
-                                    )
-                                }
-                            })
-                            .on_click(cx.listener(|_, _, window, cx| {
-                                window.dispatch_action(OpenAgentDiff.boxed_clone(), cx);
-                            })),
-                    )
-                    .child(Divider::vertical().color(DividerColor::Border))
-                    .child(
-                        Button::new("reject-all-changes", "Reject All")
-                            .label_size(LabelSize::Small)
-                            .disabled(pending_edits)
-                            .when(pending_edits, |this| {
-                                this.tooltip(Tooltip::text(EDIT_NOT_READY_TOOLTIP_LABEL))
-                            })
-                            .key_binding(
-                                KeyBinding::for_action_in(&RejectAll, &focus_handle.clone(), cx)
-                                    .map(|kb| kb.size(rems_from_px(10.))),
-                            )
-                            .on_click(cx.listener(move |this, _, window, cx| {
-                                this.reject_all(&RejectAll, window, cx);
-                            })),
+                Button::new("review-changes", "Review Changes")
+                    .label_size(LabelSize::Small)
+                    .key_binding(
+                        KeyBinding::for_action_in(&git_ui::project_diff::Diff, &focus_handle, cx)
+                            .map(|kb| kb.size(rems_from_px(10.))),
                     )
-                    .child(
-                        Button::new("keep-all-changes", "Keep All")
-                            .label_size(LabelSize::Small)
-                            .disabled(pending_edits)
-                            .when(pending_edits, |this| {
-                                this.tooltip(Tooltip::text(EDIT_NOT_READY_TOOLTIP_LABEL))
-                            })
-                            .key_binding(
-                                KeyBinding::for_action_in(&KeepAll, &focus_handle, cx)
-                                    .map(|kb| kb.size(rems_from_px(10.))),
-                            )
-                            .on_click(cx.listener(move |this, _, window, cx| {
-                                this.keep_all(&KeepAll, window, cx);
-                            })),
-                    ),
+                    .on_click(cx.listener(move |_, _, window, cx| {
+                        window.dispatch_action(git_ui::project_diff::Diff.boxed_clone(), cx);
+                    })),
             )
     }
 
     fn render_edited_files(
         &self,
-        action_log: &Entity<ActionLog>,
-        telemetry: ActionLogTelemetry,
         changed_buffers: &BTreeMap<Entity<Buffer>, Entity<BufferDiff>>,
-        pending_edits: bool,
         cx: &Context<Self>,
     ) -> impl IntoElement {
         let editor_bg_color = cx.theme().colors().editor_background;
@@ -4843,56 +4789,27 @@ impl AcpThreadView {
                                             .label_size(LabelSize::Small)
                                             .on_click({
                                                 let buffer = buffer.clone();
-                                                cx.listener(move |this, _, window, cx| {
-                                                    this.open_edited_buffer(&buffer, window, cx);
-                                                })
-                                            }),
-                                    )
-                                    .child(Divider::vertical().color(DividerColor::BorderVariant))
-                                    .child(
-                                        Button::new("reject-file", "Reject")
-                                            .label_size(LabelSize::Small)
-                                            .disabled(pending_edits)
-                                            .on_click({
-                                                let buffer = buffer.clone();
-                                                let action_log = action_log.clone();
-                                                let telemetry = telemetry.clone();
-                                                move |_, _, cx| {
-                                                    action_log.update(cx, |action_log, cx| {
-                                                        action_log
-                                                    .reject_edits_in_ranges(
-                                                        buffer.clone(),
-                                                        vec![Anchor::min_max_range_for_buffer(
-                                                            buffer.read(cx).remote_id(),
-                                                        )],
-                                                        Some(telemetry.clone()),
-                                                        cx,
-                                                    )
-                                                    .detach_and_log_err(cx);
-                                                    })
-                                                }
-                                            }),
-                                    )
-                                    .child(
-                                        Button::new("keep-file", "Keep")
-                                            .label_size(LabelSize::Small)
-                                            .disabled(pending_edits)
-                                            .on_click({
-                                                let buffer = buffer.clone();
-                                                let action_log = action_log.clone();
-                                                let telemetry = telemetry.clone();
-                                                move |_, _, cx| {
-                                                    action_log.update(cx, |action_log, cx| {
-                                                        action_log.keep_edits_in_range(
-                                                            buffer.clone(),
-                                                            Anchor::min_max_range_for_buffer(
-                                                                buffer.read(cx).remote_id(),
-                                                            ),
-                                                            Some(telemetry.clone()),
+                                                let workspace = self.workspace.clone();
+                                                cx.listener(move |_, _, window, cx| {
+                                                    let Some(workspace) = workspace.upgrade() else {
+                                                        return;
+                                                    };
+                                                    let Some(file) = buffer.read(cx).file() else {
+                                                        return;
+                                                    };
+                                                    let project_path = project::ProjectPath {
+                                                        worktree_id: file.worktree_id(cx),
+                                                        path: file.path().clone(),
+                                                    };
+                                                    workspace.update(cx, |workspace, cx| {
+                                                        git_ui::project_diff::ProjectDiff::deploy_at_project_path(
+                                                            workspace,
+                                                            project_path,
+                                                            window,
                                                             cx,
                                                         );
-                                                    })
-                                                }
+                                                    });
+                                                })
                                             }),
                                     ),
                             );
@@ -7291,6 +7208,7 @@ fn terminal_command_markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
 #[cfg(test)]
 pub(crate) mod tests {
     use acp_thread::StubAgentConnection;
+    use action_log::ActionLog;
     use agent_client_protocol::SessionId;
     use editor::MultiBufferOffset;
     use fs::FakeFs;

crates/git_ui/src/project_diff.rs 🔗

@@ -182,6 +182,37 @@ impl ProjectDiff {
         }
     }
 
+    pub fn deploy_at_project_path(
+        workspace: &mut Workspace,
+        project_path: ProjectPath,
+        window: &mut Window,
+        cx: &mut Context<Workspace>,
+    ) {
+        telemetry::event!("Git Diff Opened", source = "Agent Panel");
+        let existing = workspace
+            .items_of_type::<Self>(cx)
+            .find(|item| matches!(item.read(cx).diff_base(cx), DiffBase::Head));
+        let project_diff = if let Some(existing) = existing {
+            workspace.activate_item(&existing, true, true, window, cx);
+            existing
+        } else {
+            let workspace_handle = cx.entity();
+            let project_diff =
+                cx.new(|cx| Self::new(workspace.project().clone(), workspace_handle, window, cx));
+            workspace.add_item_to_active_pane(
+                Box::new(project_diff.clone()),
+                None,
+                true,
+                window,
+                cx,
+            );
+            project_diff
+        };
+        project_diff.update(cx, |project_diff, cx| {
+            project_diff.move_to_project_path(&project_path, window, cx);
+        });
+    }
+
     pub fn autoscroll(&self, cx: &mut Context<Self>) {
         self.editor.update(cx, |editor, cx| {
             editor.primary_editor().update(cx, |editor, cx| {
@@ -356,6 +387,31 @@ impl ProjectDiff {
         self.move_to_path(path_key, window, cx)
     }
 
+    pub fn move_to_project_path(
+        &mut self,
+        project_path: &ProjectPath,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        let Some(git_repo) = self.branch_diff.read(cx).repo() else {
+            return;
+        };
+        let Some(repo_path) = git_repo
+            .read(cx)
+            .project_path_to_repo_path(project_path, cx)
+        else {
+            return;
+        };
+        let status = git_repo
+            .read(cx)
+            .status_for_path(&repo_path)
+            .map(|entry| entry.status)
+            .unwrap_or(FileStatus::Untracked);
+        let sort_prefix = sort_prefix(&git_repo.read(cx), &repo_path, status, cx);
+        let path_key = PathKey::with_sort_prefix(sort_prefix, repo_path.as_ref().clone());
+        self.move_to_path(path_key, window, cx)
+    }
+
     pub fn active_path(&self, cx: &App) -> Option<ProjectPath> {
         let editor = self.editor.read(cx).last_selected_editor().read(cx);
         let position = editor.selections.newest_anchor().head();