Skip to content

Commit 7fe6188

Browse files
authored
Introduce "Keep All" and "Reject All" buttons when reviewing assistant edits (#27724)
Release Notes: - N/A
1 parent 8add90d commit 7fe6188

File tree

7 files changed

+182
-29
lines changed

7 files changed

+182
-29
lines changed

crates/assistant2/src/assistant.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ pub use crate::assistant_panel::{AssistantPanel, ConcreteAssistantPanelDelegate}
4040
pub use crate::inline_assistant::InlineAssistant;
4141
pub use crate::thread::{Message, RequestKind, Thread, ThreadEvent};
4242
pub use crate::thread_store::ThreadStore;
43-
pub use assistant_diff::AssistantDiff;
43+
pub use assistant_diff::{AssistantDiff, AssistantDiffToolbar};
4444

4545
actions!(
4646
assistant2,
@@ -66,7 +66,9 @@ actions!(
6666
AcceptSuggestedContext,
6767
OpenActiveThreadAsMarkdown,
6868
ToggleKeep,
69-
Reject
69+
Reject,
70+
RejectAll,
71+
KeepAll
7072
]
7173
);
7274

crates/assistant2/src/assistant_diff.rs

+143-27
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,23 @@ use editor::{
77
Direction, Editor, EditorEvent, MultiBuffer, ToPoint,
88
};
99
use gpui::{
10-
prelude::*, AnyElement, AnyView, App, Entity, EventEmitter, FocusHandle, Focusable,
10+
prelude::*, Action, AnyElement, AnyView, App, Entity, EventEmitter, FocusHandle, Focusable,
1111
SharedString, Subscription, Task, WeakEntity, Window,
1212
};
13-
use language::{Capability, DiskState, OffsetRangeExt};
13+
use language::{Capability, DiskState, OffsetRangeExt, Point};
1414
use multi_buffer::PathKey;
1515
use project::{Project, ProjectPath};
1616
use std::{
1717
any::{Any, TypeId},
1818
ops::Range,
1919
sync::Arc,
2020
};
21-
use ui::{prelude::*, IconButtonShape, Tooltip};
21+
use ui::{prelude::*, IconButtonShape, KeyBinding, Tooltip};
2222
use workspace::{
2323
item::{BreadcrumbText, ItemEvent, TabContentParams},
2424
searchable::SearchableItemHandle,
25-
Item, ItemHandle, ItemNavHistory, ToolbarItemLocation, Workspace,
25+
Item, ItemHandle, ItemNavHistory, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView,
26+
Workspace,
2627
};
2728

2829
pub struct AssistantDiff {
@@ -78,6 +79,7 @@ impl AssistantDiff {
7879
is_created_file,
7980
line_height,
8081
_editor: &Entity<Editor>,
82+
window: &mut Window,
8183
cx: &mut App| {
8284
render_diff_hunk_controls(
8385
row,
@@ -86,6 +88,7 @@ impl AssistantDiff {
8688
is_created_file,
8789
line_height,
8890
&assistant_diff,
91+
window,
8992
cx,
9093
)
9194
}
@@ -253,6 +256,18 @@ impl AssistantDiff {
253256
})
254257
}
255258

259+
fn reject_all(&mut self, _: &crate::RejectAll, window: &mut Window, cx: &mut Context<Self>) {
260+
self.editor.update(cx, |editor, cx| {
261+
let max_point = editor.buffer().read(cx).read(cx).max_point();
262+
editor.restore_hunks_in_ranges(vec![Point::zero()..max_point], window, cx)
263+
})
264+
}
265+
266+
fn keep_all(&mut self, _: &crate::KeepAll, _window: &mut Window, cx: &mut Context<Self>) {
267+
self.thread
268+
.update(cx, |thread, cx| thread.keep_all_edits(cx));
269+
}
270+
256271
fn review_diff_hunks(
257272
&mut self,
258273
hunk_ranges: Vec<Range<editor::Anchor>>,
@@ -465,6 +480,8 @@ impl Render for AssistantDiff {
465480
})
466481
.on_action(cx.listener(Self::toggle_keep))
467482
.on_action(cx.listener(Self::reject))
483+
.on_action(cx.listener(Self::reject_all))
484+
.on_action(cx.listener(Self::keep_all))
468485
.bg(cx.theme().colors().editor_background)
469486
.flex()
470487
.items_center()
@@ -482,6 +499,7 @@ fn render_diff_hunk_controls(
482499
is_created_file: bool,
483500
line_height: Pixels,
484501
assistant_diff: &Entity<AssistantDiff>,
502+
window: &mut Window,
485503
cx: &mut App,
486504
) -> AnyElement {
487505
let editor = assistant_diff.read(cx).editor.clone();
@@ -501,55 +519,67 @@ fn render_diff_hunk_controls(
501519
.shadow_md()
502520
.children(if status.has_secondary_hunk() {
503521
vec![
504-
Button::new(("keep", row as u64), "Keep")
522+
Button::new("reject", "Reject")
523+
.key_binding(KeyBinding::for_action_in(
524+
&crate::Reject,
525+
&editor.read(cx).focus_handle(cx),
526+
window,
527+
cx,
528+
))
505529
.tooltip({
506530
let focus_handle = editor.focus_handle(cx);
507531
move |window, cx| {
508532
Tooltip::for_action_in(
509-
"Keep Hunk",
510-
&crate::ToggleKeep,
533+
"Reject Hunk",
534+
&crate::Reject,
511535
&focus_handle,
512536
window,
513537
cx,
514538
)
515539
}
516540
})
517541
.on_click({
518-
let assistant_diff = assistant_diff.clone();
519-
move |_event, _window, cx| {
520-
assistant_diff.update(cx, |diff, cx| {
521-
diff.review_diff_hunks(
522-
vec![hunk_range.start..hunk_range.start],
523-
true,
524-
cx,
525-
);
542+
let editor = editor.clone();
543+
move |_event, window, cx| {
544+
editor.update(cx, |editor, cx| {
545+
let snapshot = editor.snapshot(window, cx);
546+
let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
547+
editor.restore_hunks_in_ranges(vec![point..point], window, cx);
526548
});
527549
}
528-
}),
529-
Button::new("reject", "Reject")
550+
})
551+
.disabled(is_created_file),
552+
Button::new(("keep", row as u64), "Keep")
553+
.key_binding(KeyBinding::for_action_in(
554+
&crate::ToggleKeep,
555+
&editor.read(cx).focus_handle(cx),
556+
window,
557+
cx,
558+
))
530559
.tooltip({
531560
let focus_handle = editor.focus_handle(cx);
532561
move |window, cx| {
533562
Tooltip::for_action_in(
534-
"Reject Hunk",
535-
&crate::Reject,
563+
"Keep Hunk",
564+
&crate::ToggleKeep,
536565
&focus_handle,
537566
window,
538567
cx,
539568
)
540569
}
541570
})
542571
.on_click({
543-
let editor = editor.clone();
544-
move |_event, window, cx| {
545-
editor.update(cx, |editor, cx| {
546-
let snapshot = editor.snapshot(window, cx);
547-
let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
548-
editor.restore_hunks_in_ranges(vec![point..point], window, cx);
572+
let assistant_diff = assistant_diff.clone();
573+
move |_event, _window, cx| {
574+
assistant_diff.update(cx, |diff, cx| {
575+
diff.review_diff_hunks(
576+
vec![hunk_range.start..hunk_range.start],
577+
true,
578+
cx,
579+
);
549580
});
550581
}
551-
})
552-
.disabled(is_created_file),
582+
}),
553583
]
554584
} else {
555585
vec![Button::new(("review", row as u64), "Review")
@@ -663,3 +693,89 @@ impl editor::Addon for AssistantDiffAddon {
663693
key_context.add("assistant_diff");
664694
}
665695
}
696+
697+
pub struct AssistantDiffToolbar {
698+
assistant_diff: Option<WeakEntity<AssistantDiff>>,
699+
_workspace: WeakEntity<Workspace>,
700+
}
701+
702+
impl AssistantDiffToolbar {
703+
pub fn new(workspace: &Workspace, _: &mut Context<Self>) -> Self {
704+
Self {
705+
assistant_diff: None,
706+
_workspace: workspace.weak_handle(),
707+
}
708+
}
709+
710+
fn assistant_diff(&self, _: &App) -> Option<Entity<AssistantDiff>> {
711+
self.assistant_diff.as_ref()?.upgrade()
712+
}
713+
714+
fn dispatch_action(&self, action: &dyn Action, window: &mut Window, cx: &mut Context<Self>) {
715+
if let Some(assistant_diff) = self.assistant_diff(cx) {
716+
assistant_diff.focus_handle(cx).focus(window);
717+
}
718+
let action = action.boxed_clone();
719+
cx.defer(move |cx| {
720+
cx.dispatch_action(action.as_ref());
721+
})
722+
}
723+
}
724+
725+
impl EventEmitter<ToolbarItemEvent> for AssistantDiffToolbar {}
726+
727+
impl ToolbarItemView for AssistantDiffToolbar {
728+
fn set_active_pane_item(
729+
&mut self,
730+
active_pane_item: Option<&dyn ItemHandle>,
731+
_: &mut Window,
732+
cx: &mut Context<Self>,
733+
) -> ToolbarItemLocation {
734+
self.assistant_diff = active_pane_item
735+
.and_then(|item| item.act_as::<AssistantDiff>(cx))
736+
.map(|entity| entity.downgrade());
737+
if self.assistant_diff.is_some() {
738+
ToolbarItemLocation::PrimaryRight
739+
} else {
740+
ToolbarItemLocation::Hidden
741+
}
742+
}
743+
744+
fn pane_focus_update(
745+
&mut self,
746+
_pane_focused: bool,
747+
_window: &mut Window,
748+
_cx: &mut Context<Self>,
749+
) {
750+
}
751+
}
752+
753+
impl Render for AssistantDiffToolbar {
754+
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
755+
if self.assistant_diff(cx).is_none() {
756+
return div();
757+
}
758+
759+
h_group_xl()
760+
.my_neg_1()
761+
.items_center()
762+
.py_1()
763+
.pl_2()
764+
.pr_1()
765+
.flex_wrap()
766+
.justify_between()
767+
.child(
768+
h_group_sm()
769+
.child(
770+
Button::new("reject-all", "Reject All").on_click(cx.listener(
771+
|this, _, window, cx| {
772+
this.dispatch_action(&crate::RejectAll, window, cx)
773+
},
774+
)),
775+
)
776+
.child(Button::new("keep-all", "Keep All").on_click(cx.listener(
777+
|this, _, window, cx| this.dispatch_action(&crate::KeepAll, window, cx),
778+
))),
779+
)
780+
}
781+
}

crates/assistant2/src/thread.rs

+7
Original file line numberDiff line numberDiff line change
@@ -1542,6 +1542,13 @@ impl Thread {
15421542
});
15431543
}
15441544

1545+
/// Keeps all edits across all buffers at once.
1546+
/// This provides a more performant alternative to calling review_edits_in_range for each buffer.
1547+
pub fn keep_all_edits(&mut self, cx: &mut Context<Self>) {
1548+
self.action_log
1549+
.update(cx, |action_log, _cx| action_log.keep_all_edits());
1550+
}
1551+
15451552
pub fn action_log(&self) -> &Entity<ActionLog> {
15461553
&self.action_log
15471554
}

crates/assistant_tool/src/action_log.rs

+22
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,28 @@ impl ActionLog {
353353
tracked_buffer.schedule_diff_update();
354354
}
355355

356+
/// Keep all edits across all buffers.
357+
/// This is a more performant alternative to calling review_edits_in_range for each buffer.
358+
pub fn keep_all_edits(&mut self) {
359+
// Process all tracked buffers
360+
for (_, tracked_buffer) in self.tracked_buffers.iter_mut() {
361+
match &mut tracked_buffer.change {
362+
Change::Deleted { reviewed, .. } => {
363+
*reviewed = true;
364+
}
365+
Change::Edited {
366+
unreviewed_edit_ids,
367+
accepted_edit_ids,
368+
..
369+
} => {
370+
accepted_edit_ids.extend(unreviewed_edit_ids.drain());
371+
}
372+
}
373+
374+
tracked_buffer.schedule_diff_update();
375+
}
376+
}
377+
356378
/// Returns the set of buffers that contain changes that haven't been reviewed by the user.
357379
pub fn changed_buffers(&self, cx: &App) -> BTreeMap<Entity<Buffer>, ChangedBuffer> {
358380
self.tracked_buffers

crates/editor/src/editor.rs

+2
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ pub type RenderDiffHunkControlsFn = Arc<
228228
bool,
229229
Pixels,
230230
&Entity<Editor>,
231+
&mut Window,
231232
&mut App,
232233
) -> AnyElement,
233234
>;
@@ -20011,6 +20012,7 @@ fn render_diff_hunk_controls(
2001120012
is_created_file: bool,
2001220013
line_height: Pixels,
2001320014
editor: &Entity<Editor>,
20015+
_window: &mut Window,
2001420016
cx: &mut App,
2001520017
) -> AnyElement {
2001620018
h_flex()

crates/editor/src/element.rs

+1
Original file line numberDiff line numberDiff line change
@@ -4013,6 +4013,7 @@ impl EditorElement {
40134013
*is_created_file,
40144014
line_height,
40154015
&editor,
4016+
window,
40164017
cx,
40174018
);
40184019
let size =

crates/zed/src/zed.rs

+3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ pub(crate) mod windows_only_instance;
1111
use anyhow::Context as _;
1212
pub use app_menus::*;
1313
use assets::Assets;
14+
use assistant2::AssistantDiffToolbar;
1415
use assistant_context_editor::AssistantPanelDelegate;
1516
use breadcrumbs::Breadcrumbs;
1617
use client::{zed_urls, ZED_URL_SCHEME};
@@ -939,6 +940,8 @@ fn initialize_pane(
939940
toolbar.add_item(migration_banner, window, cx);
940941
let project_diff_toolbar = cx.new(|cx| ProjectDiffToolbar::new(workspace, cx));
941942
toolbar.add_item(project_diff_toolbar, window, cx);
943+
let assistant_diff_toolbar = cx.new(|cx| AssistantDiffToolbar::new(workspace, cx));
944+
toolbar.add_item(assistant_diff_toolbar, window, cx);
942945
})
943946
});
944947
}

0 commit comments

Comments
 (0)