diff --git a/Cargo.lock b/Cargo.lock index 0e0e47c1ddf80650c355734f800f8da9bcc0c074..bcd278fd439ab0fa0fa9e3d2a5e676cbb200a189 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -362,6 +362,7 @@ dependencies = [ "fs", "futures 0.3.31", "fuzzy", + "git_ui", "gpui", "gpui_tokio", "html_to_markdown", diff --git a/crates/agent_ui/Cargo.toml b/crates/agent_ui/Cargo.toml index 5b2cbf58fcfb82452b1555702a2e33f95c5e082e..66fd7295a058f1f2ec2ee74d6be78133e7c88219 100644 --- a/crates/agent_ui/Cargo.toml +++ b/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 diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index c7cb6860e4097562abd6f06729c9b0472caeb067..9a9c912408370c469bf6ef0362891cafdf522be5 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/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 { 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, ) -> 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, - telemetry: ActionLogTelemetry, changed_buffers: &BTreeMap, Entity>, - pending_edits: bool, cx: &Context, ) -> 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; diff --git a/crates/git_ui/src/project_diff.rs b/crates/git_ui/src/project_diff.rs index ec06f6776cf9dddc710558810686e54aa1aa899e..c1ee5dff9217404e73464e71152546de7640209b 100644 --- a/crates/git_ui/src/project_diff.rs +++ b/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, + ) { + telemetry::event!("Git Diff Opened", source = "Agent Panel"); + let existing = workspace + .items_of_type::(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.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, + ) { + 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 { let editor = self.editor.read(cx).last_selected_editor().read(cx); let position = editor.selections.newest_anchor().head();