Introduce "Keep All" and "Reject All" buttons when reviewing assistant edits (#27724)

Release Notes:

- N/A
This commit is contained in:
Antonio Scandurra 2025-03-29 10:28:11 +01:00 committed by GitHub
parent 8add90d7cb
commit 7fe6188f8e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 183 additions and 30 deletions

View file

@ -40,7 +40,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;
pub use assistant_diff::{AssistantDiff, AssistantDiffToolbar};
actions!(
assistant2,
@ -66,7 +66,9 @@ actions!(
AcceptSuggestedContext,
OpenActiveThreadAsMarkdown,
ToggleKeep,
Reject
Reject,
RejectAll,
KeepAll
]
);

View file

@ -7,10 +7,10 @@ use editor::{
Direction, Editor, EditorEvent, MultiBuffer, ToPoint,
};
use gpui::{
prelude::*, AnyElement, AnyView, App, Entity, EventEmitter, FocusHandle, Focusable,
prelude::*, Action, AnyElement, AnyView, App, Entity, EventEmitter, FocusHandle, Focusable,
SharedString, Subscription, Task, WeakEntity, Window,
};
use language::{Capability, DiskState, OffsetRangeExt};
use language::{Capability, DiskState, OffsetRangeExt, Point};
use multi_buffer::PathKey;
use project::{Project, ProjectPath};
use std::{
@ -18,11 +18,12 @@ use std::{
ops::Range,
sync::Arc,
};
use ui::{prelude::*, IconButtonShape, Tooltip};
use ui::{prelude::*, IconButtonShape, KeyBinding, Tooltip};
use workspace::{
item::{BreadcrumbText, ItemEvent, TabContentParams},
searchable::SearchableItemHandle,
Item, ItemHandle, ItemNavHistory, ToolbarItemLocation, Workspace,
Item, ItemHandle, ItemNavHistory, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView,
Workspace,
};
pub struct AssistantDiff {
@ -78,6 +79,7 @@ impl AssistantDiff {
is_created_file,
line_height,
_editor: &Entity<Editor>,
window: &mut Window,
cx: &mut App| {
render_diff_hunk_controls(
row,
@ -86,6 +88,7 @@ impl AssistantDiff {
is_created_file,
line_height,
&assistant_diff,
window,
cx,
)
}
@ -253,6 +256,18 @@ impl AssistantDiff {
})
}
fn reject_all(&mut self, _: &crate::RejectAll, window: &mut Window, cx: &mut Context<Self>) {
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)
})
}
fn keep_all(&mut self, _: &crate::KeepAll, _window: &mut Window, cx: &mut Context<Self>) {
self.thread
.update(cx, |thread, cx| thread.keep_all_edits(cx));
}
fn review_diff_hunks(
&mut self,
hunk_ranges: Vec<Range<editor::Anchor>>,
@ -465,6 +480,8 @@ impl Render for AssistantDiff {
})
.on_action(cx.listener(Self::toggle_keep))
.on_action(cx.listener(Self::reject))
.on_action(cx.listener(Self::reject_all))
.on_action(cx.listener(Self::keep_all))
.bg(cx.theme().colors().editor_background)
.flex()
.items_center()
@ -482,6 +499,7 @@ fn render_diff_hunk_controls(
is_created_file: bool,
line_height: Pixels,
assistant_diff: &Entity<AssistantDiff>,
window: &mut Window,
cx: &mut App,
) -> AnyElement {
let editor = assistant_diff.read(cx).editor.clone();
@ -501,7 +519,43 @@ fn render_diff_hunk_controls(
.shadow_md()
.children(if status.has_secondary_hunk() {
vec![
Button::new("reject", "Reject")
.key_binding(KeyBinding::for_action_in(
&crate::Reject,
&editor.read(cx).focus_handle(cx),
window,
cx,
))
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |window, cx| {
Tooltip::for_action_in(
"Reject Hunk",
&crate::Reject,
&focus_handle,
window,
cx,
)
}
})
.on_click({
let editor = editor.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);
});
}
})
.disabled(is_created_file),
Button::new(("keep", row as u64), "Keep")
.key_binding(KeyBinding::for_action_in(
&crate::ToggleKeep,
&editor.read(cx).focus_handle(cx),
window,
cx,
))
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |window, cx| {
@ -526,30 +580,6 @@ fn render_diff_hunk_controls(
});
}
}),
Button::new("reject", "Reject")
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |window, cx| {
Tooltip::for_action_in(
"Reject Hunk",
&crate::Reject,
&focus_handle,
window,
cx,
)
}
})
.on_click({
let editor = editor.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);
});
}
})
.disabled(is_created_file),
]
} else {
vec![Button::new(("review", row as u64), "Review")
@ -663,3 +693,89 @@ impl editor::Addon for AssistantDiffAddon {
key_context.add("assistant_diff");
}
}
pub struct AssistantDiffToolbar {
assistant_diff: Option<WeakEntity<AssistantDiff>>,
_workspace: WeakEntity<Workspace>,
}
impl AssistantDiffToolbar {
pub fn new(workspace: &Workspace, _: &mut Context<Self>) -> Self {
Self {
assistant_diff: None,
_workspace: workspace.weak_handle(),
}
}
fn assistant_diff(&self, _: &App) -> Option<Entity<AssistantDiff>> {
self.assistant_diff.as_ref()?.upgrade()
}
fn dispatch_action(&self, action: &dyn Action, window: &mut Window, cx: &mut Context<Self>) {
if let Some(assistant_diff) = self.assistant_diff(cx) {
assistant_diff.focus_handle(cx).focus(window);
}
let action = action.boxed_clone();
cx.defer(move |cx| {
cx.dispatch_action(action.as_ref());
})
}
}
impl EventEmitter<ToolbarItemEvent> for AssistantDiffToolbar {}
impl ToolbarItemView for AssistantDiffToolbar {
fn set_active_pane_item(
&mut self,
active_pane_item: Option<&dyn ItemHandle>,
_: &mut Window,
cx: &mut Context<Self>,
) -> ToolbarItemLocation {
self.assistant_diff = active_pane_item
.and_then(|item| item.act_as::<AssistantDiff>(cx))
.map(|entity| entity.downgrade());
if self.assistant_diff.is_some() {
ToolbarItemLocation::PrimaryRight
} else {
ToolbarItemLocation::Hidden
}
}
fn pane_focus_update(
&mut self,
_pane_focused: bool,
_window: &mut Window,
_cx: &mut Context<Self>,
) {
}
}
impl Render for AssistantDiffToolbar {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
if self.assistant_diff(cx).is_none() {
return div();
}
h_group_xl()
.my_neg_1()
.items_center()
.py_1()
.pl_2()
.pr_1()
.flex_wrap()
.justify_between()
.child(
h_group_sm()
.child(
Button::new("reject-all", "Reject All").on_click(cx.listener(
|this, _, window, cx| {
this.dispatch_action(&crate::RejectAll, window, cx)
},
)),
)
.child(Button::new("keep-all", "Keep All").on_click(cx.listener(
|this, _, window, cx| this.dispatch_action(&crate::KeepAll, window, cx),
))),
)
}
}

View file

@ -1542,6 +1542,13 @@ impl Thread {
});
}
/// Keeps all edits across all buffers at once.
/// This provides a more performant alternative to calling review_edits_in_range for each buffer.
pub fn keep_all_edits(&mut self, cx: &mut Context<Self>) {
self.action_log
.update(cx, |action_log, _cx| action_log.keep_all_edits());
}
pub fn action_log(&self) -> &Entity<ActionLog> {
&self.action_log
}

View file

@ -353,6 +353,28 @@ impl ActionLog {
tracked_buffer.schedule_diff_update();
}
/// Keep all edits across all buffers.
/// This is a more performant alternative to calling review_edits_in_range for each buffer.
pub fn keep_all_edits(&mut self) {
// Process all tracked buffers
for (_, tracked_buffer) in self.tracked_buffers.iter_mut() {
match &mut tracked_buffer.change {
Change::Deleted { reviewed, .. } => {
*reviewed = true;
}
Change::Edited {
unreviewed_edit_ids,
accepted_edit_ids,
..
} => {
accepted_edit_ids.extend(unreviewed_edit_ids.drain());
}
}
tracked_buffer.schedule_diff_update();
}
}
/// Returns the set of buffers that contain changes that haven't been reviewed by the user.
pub fn changed_buffers(&self, cx: &App) -> BTreeMap<Entity<Buffer>, ChangedBuffer> {
self.tracked_buffers

View file

@ -228,6 +228,7 @@ pub type RenderDiffHunkControlsFn = Arc<
bool,
Pixels,
&Entity<Editor>,
&mut Window,
&mut App,
) -> AnyElement,
>;
@ -20011,6 +20012,7 @@ fn render_diff_hunk_controls(
is_created_file: bool,
line_height: Pixels,
editor: &Entity<Editor>,
_window: &mut Window,
cx: &mut App,
) -> AnyElement {
h_flex()

View file

@ -4013,6 +4013,7 @@ impl EditorElement {
*is_created_file,
line_height,
&editor,
window,
cx,
);
let size =

View file

@ -11,6 +11,7 @@ pub(crate) mod windows_only_instance;
use anyhow::Context as _;
pub use app_menus::*;
use assets::Assets;
use assistant2::AssistantDiffToolbar;
use assistant_context_editor::AssistantPanelDelegate;
use breadcrumbs::Breadcrumbs;
use client::{zed_urls, ZED_URL_SCHEME};
@ -939,6 +940,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);
})
});
}