diff --git a/crates/assistant2/src/assistant.rs b/crates/assistant2/src/assistant.rs index 01906f3b9a..04c8dc2f43 100644 --- a/crates/assistant2/src/assistant.rs +++ b/crates/assistant2/src/assistant.rs @@ -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 ] ); diff --git a/crates/assistant2/src/assistant_diff.rs b/crates/assistant2/src/assistant_diff.rs index 8944ba0975..c2c7561a4a 100644 --- a/crates/assistant2/src/assistant_diff.rs +++ b/crates/assistant2/src/assistant_diff.rs @@ -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, + 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.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.thread + .update(cx, |thread, cx| thread.keep_all_edits(cx)); + } + fn review_diff_hunks( &mut self, hunk_ranges: Vec>, @@ -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, + 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>, + _workspace: WeakEntity, +} + +impl AssistantDiffToolbar { + pub fn new(workspace: &Workspace, _: &mut Context) -> Self { + Self { + assistant_diff: None, + _workspace: workspace.weak_handle(), + } + } + + fn assistant_diff(&self, _: &App) -> Option> { + self.assistant_diff.as_ref()?.upgrade() + } + + fn dispatch_action(&self, action: &dyn Action, window: &mut Window, cx: &mut Context) { + 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 for AssistantDiffToolbar {} + +impl ToolbarItemView for AssistantDiffToolbar { + fn set_active_pane_item( + &mut self, + active_pane_item: Option<&dyn ItemHandle>, + _: &mut Window, + cx: &mut Context, + ) -> ToolbarItemLocation { + self.assistant_diff = active_pane_item + .and_then(|item| item.act_as::(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, + ) { + } +} + +impl Render for AssistantDiffToolbar { + fn render(&mut self, _: &mut Window, cx: &mut Context) -> 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), + ))), + ) + } +} diff --git a/crates/assistant2/src/thread.rs b/crates/assistant2/src/thread.rs index 66b50e5e7a..570194f4cf 100644 --- a/crates/assistant2/src/thread.rs +++ b/crates/assistant2/src/thread.rs @@ -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.action_log + .update(cx, |action_log, _cx| action_log.keep_all_edits()); + } + pub fn action_log(&self) -> &Entity { &self.action_log } diff --git a/crates/assistant_tool/src/action_log.rs b/crates/assistant_tool/src/action_log.rs index 6a91524afa..a455f1303e 100644 --- a/crates/assistant_tool/src/action_log.rs +++ b/crates/assistant_tool/src/action_log.rs @@ -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, ChangedBuffer> { self.tracked_buffers diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index df3b3afc10..a2b720a4b1 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -228,6 +228,7 @@ pub type RenderDiffHunkControlsFn = Arc< bool, Pixels, &Entity, + &mut Window, &mut App, ) -> AnyElement, >; @@ -20011,6 +20012,7 @@ fn render_diff_hunk_controls( is_created_file: bool, line_height: Pixels, editor: &Entity, + _window: &mut Window, cx: &mut App, ) -> AnyElement { h_flex() diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index d99109d44b..b5c07e2bc3 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -4013,6 +4013,7 @@ impl EditorElement { *is_created_file, line_height, &editor, + window, cx, ); let size = diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 4d4284230c..92afbc042e 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -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); }) }); }