diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json index b8be0741e647ebb7c5934377a854a2f387365caa..e4dbb077e41eec089be232d2b4f2cf1e67318070 100644 --- a/assets/keymaps/default-linux.json +++ b/assets/keymaps/default-linux.json @@ -303,6 +303,7 @@ "use_key_equivalents": true, "bindings": { "ctrl-shift-r": "agent::OpenAgentDiff", + "ctrl-shift-d": "git::Diff", "shift-alt-y": "agent::KeepAll", "shift-alt-z": "agent::RejectAll", "ctrl-enter": "agent::ChatWithFollow", diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json index 98aa9db242eda0bfbb16064b1204674b26f66d31..92b321c933bb80563ab0ddd5495c4491e7c73d54 100644 --- a/assets/keymaps/default-macos.json +++ b/assets/keymaps/default-macos.json @@ -351,6 +351,7 @@ "use_key_equivalents": true, "bindings": { "shift-ctrl-r": "agent::OpenAgentDiff", + "shift-ctrl-d": "git::Diff", "shift-alt-y": "agent::KeepAll", "shift-alt-z": "agent::RejectAll", "cmd-enter": "agent::ChatWithFollow", diff --git a/assets/keymaps/default-windows.json b/assets/keymaps/default-windows.json index 66e7f68d9cd601c5c4ae099266bc5ffa2b65f940..519d5111054dcfbd6c3de37fc900c0cada897caa 100644 --- a/assets/keymaps/default-windows.json +++ b/assets/keymaps/default-windows.json @@ -305,6 +305,7 @@ "use_key_equivalents": true, "bindings": { "ctrl-shift-r": "agent::OpenAgentDiff", + "ctrl-shift-d": "git::Diff", "shift-alt-y": "agent::KeepAll", "shift-alt-z": "agent::RejectAll", "ctrl-enter": "agent::ChatWithFollow", diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index 4d05600aad649786739ac010b9066508d0c04a71..809b6c8cb26ca4767ad462277c71ed9815e0fd44 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -364,6 +364,7 @@ pub struct AcpThreadView { last_turn_duration: Option, turn_generation: usize, _turn_timer_task: Option>, + hovered_edited_file_buttons: Option, } struct QueuedMessage { @@ -558,6 +559,7 @@ impl AcpThreadView { last_turn_duration: None, turn_generation: 0, _turn_timer_task: None, + hovered_edited_file_buttons: None, } } @@ -5470,6 +5472,146 @@ impl AcpThreadView { }) } + fn render_edited_files_buttons( + &self, + index: usize, + buffer: &Entity, + action_log: &Entity, + telemetry: &ActionLogTelemetry, + pending_edits: bool, + use_keep_reject_buttons: bool, + editor_bg_color: Hsla, + cx: &Context, + ) -> impl IntoElement { + let container = h_flex() + .id("edited-buttons-container") + .visible_on_hover("edited-code") + .absolute() + .right_0() + .px_1() + .gap_1() + .bg(editor_bg_color) + .on_hover(cx.listener(move |this, is_hovered, _window, cx| { + if *is_hovered { + this.hovered_edited_file_buttons = Some(index); + } else if this.hovered_edited_file_buttons == Some(index) { + this.hovered_edited_file_buttons = None; + } + cx.notify(); + })); + + if use_keep_reject_buttons { + container + .child( + Button::new(("review", index), "Review") + .label_size(LabelSize::Small) + .on_click({ + let buffer = buffer.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, + ); + }); + }) + }), + ) + .child(Divider::vertical().color(DividerColor::BorderVariant)) + .child( + Button::new(("reject-file", index), "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", index), "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()), + cx, + ); + }) + } + }), + ) + .into_any_element() + } else { + container + .child( + Button::new(("review", index), "Review") + .label_size(LabelSize::Small) + .on_click({ + let buffer = buffer.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, + ); + }); + }) + }), + ) + .into_any_element() + } + } + fn render_edited_files( &self, action_log: &Entity, @@ -5529,20 +5671,25 @@ impl AcpThreadView { .size(IconSize::Small) }); - let overlay_gradient = linear_gradient( - 90., - linear_color_stop(editor_bg_color, 1.), - linear_color_stop(editor_bg_color.opacity(0.2), 0.), - ); - let file_stats = DiffStats::single_file(buffer.read(cx), diff.read(cx), cx); + let buttons = self.render_edited_files_buttons( + index, + buffer, + action_log, + &telemetry, + pending_edits, + use_keep_reject_buttons, + editor_bg_color, + cx, + ); + let element = h_flex() .group("edited-code") .id(("file-container", index)) - .py_1() - .pl_2() - .pr_1() + .relative() + .min_w_0() + .p_1p5() .gap_2() .justify_between() .bg(editor_bg_color) @@ -5551,172 +5698,47 @@ impl AcpThreadView { }) .child( h_flex() - .id(("file-name-row", index)) - .relative() - .pr_8() - .w_full() + .id(("file-name-path", index)) + .cursor_pointer() + .pr_0p5() + .gap_0p5() + .rounded_xs() + .child(file_icon) + .children(file_name) + .children(file_path) .child( - h_flex() - .id(("file-name-path", index)) - .cursor_pointer() - .pr_0p5() - .gap_0p5() - .hover(|s| s.bg(cx.theme().colors().element_hover)) - .rounded_xs() - .child(file_icon) - .children(file_name) - .children(file_path) - .child( - DiffStat::new( - "file", - file_stats.lines_added as usize, - file_stats.lines_removed as usize, - ) - .label_size(LabelSize::XSmall), - ) - .tooltip(move |_, cx| { - Tooltip::with_meta( - "Go to File", - None, - full_path.clone(), - cx, - ) - }) - .on_click({ - let buffer = buffer.clone(); - cx.listener(move |this, _, window, cx| { - this.open_edited_buffer(&buffer, window, cx); - }) - }), - ) - .child( - div() - .absolute() - .h_full() - .w_12() - .top_0() - .bottom_0() - .right_0() - .bg(overlay_gradient), - ), - ) - .when(use_keep_reject_buttons, |parent| { - parent.child( - h_flex() - .gap_1() - .visible_on_hover("edited-code") - .child( - Button::new("review", "Review") - .label_size(LabelSize::Small) - .on_click({ - let buffer = buffer.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, - ); - }); - }) - }), - ) - .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); - }) - } - }), + DiffStat::new( + "file", + file_stats.lines_added as usize, + file_stats.lines_removed as usize, ) - .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()), - cx, - ); - }) - } - }), - ), - ) - }) - .when(!use_keep_reject_buttons, |parent| { - parent.child( - h_flex() - .gap_1() - .visible_on_hover("edited-code") - .child( - Button::new("review", "Review") - .label_size(LabelSize::Small) + .label_size(LabelSize::XSmall), + ) + .when( + self.hovered_edited_file_buttons != Some(index), + |this| { + let full_path = full_path.clone(); + this.hover(|s| s.bg(cx.theme().colors().element_hover)) + .tooltip(move |_, cx| { + Tooltip::with_meta( + "Go to File", + None, + full_path.clone(), + cx, + ) + }) .on_click({ let buffer = buffer.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, - ); - }); + cx.listener(move |this, _, window, cx| { + this.open_edited_buffer( + &buffer, window, cx, + ); }) - }), - ), - ) - }); + }) + }, + ), + ) + .child(buttons); Some(element) }),