Cargo.lock 🔗
@@ -362,6 +362,7 @@ dependencies = [
"fs",
"futures 0.3.31",
"fuzzy",
+ "git_ui",
"gpui",
"gpui_tokio",
"html_to_markdown",
Michael Benfield created
Release Notes:
- N/A
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(-)
@@ -362,6 +362,7 @@ dependencies = [
"fs",
"futures 0.3.31",
"fuzzy",
+ "git_ui",
"gpui",
"gpui_tokio",
"html_to_markdown",
@@ -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
@@ -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;
@@ -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();