agent: Review edits in single-file editors (#29820)

Enables reviewing agent edits from single-file editors in addition to
the multibuffer experience we already had.


https://github.com/user-attachments/assets/a2c287f0-51d6-43a1-8537-821498b91983


This feature can be turned off by setting `assistant.single_file_review:
false`.

Release Notes:

- agent: Review edits in single-file editors
This commit is contained in:
Agus Zubiaga 2025-05-02 17:57:16 -03:00 committed by GitHub
parent 04772bf17d
commit 64316309aa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 1396 additions and 254 deletions

View file

@ -981,6 +981,8 @@ pub struct Editor {
addons: HashMap<TypeId, Box<dyn Addon>>,
registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
load_diff_task: Option<Shared<Task<()>>>,
/// Whether we are temporarily displaying a diff other than git's
temporary_diff_override: bool,
selection_mark_mode: bool,
toggle_fold_multiple_buffers: Task<()>,
_scroll_cursor_center_top_bottom_task: Task<()>,
@ -1626,7 +1628,8 @@ impl Editor {
let mut load_uncommitted_diff = None;
if let Some(project) = project.clone() {
load_uncommitted_diff = Some(
get_uncommitted_diff_for_buffer(
update_uncommitted_diff_for_buffer(
cx.entity(),
&project,
buffer.read(cx).all_buffers(),
buffer.clone(),
@ -1802,6 +1805,7 @@ impl Editor {
serialize_folds: Task::ready(()),
text_style_refinement: None,
load_diff_task: load_uncommitted_diff,
temporary_diff_override: false,
mouse_cursor_hidden: false,
hide_mouse_mode: EditorSettings::get_global(cx)
.hide_mouse
@ -1941,7 +1945,7 @@ impl Editor {
.is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
}
fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
self.key_context_internal(self.has_active_inline_completion(), window, cx)
}
@ -13649,7 +13653,7 @@ impl Editor {
self.refresh_inline_completion(false, true, window, cx);
}
fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
let snapshot = self.snapshot(window, cx);
let selection = self.selections.newest::<Point>(cx);
@ -17820,7 +17824,8 @@ impl Editor {
let buffer_id = buffer.read(cx).remote_id();
if self.buffer.read(cx).diff_for(buffer_id).is_none() {
if let Some(project) = &self.project {
get_uncommitted_diff_for_buffer(
update_uncommitted_diff_for_buffer(
cx.entity(),
project,
[buffer.clone()],
self.buffer.clone(),
@ -17896,6 +17901,32 @@ impl Editor {
};
}
pub fn start_temporary_diff_override(&mut self) {
self.load_diff_task.take();
self.temporary_diff_override = true;
}
pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
self.temporary_diff_override = false;
self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
self.buffer.update(cx, |buffer, cx| {
buffer.set_all_diff_hunks_collapsed(cx);
});
if let Some(project) = self.project.clone() {
self.load_diff_task = Some(
update_uncommitted_diff_for_buffer(
cx.entity(),
&project,
self.buffer.read(cx).all_buffers(),
self.buffer.clone(),
cx,
)
.shared(),
);
}
}
fn on_display_map_changed(
&mut self,
_: Entity<DisplayMap>,
@ -18875,7 +18906,8 @@ fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<u
.all(|c| c.is_whitespace() && c != '\n')
}
fn get_uncommitted_diff_for_buffer(
fn update_uncommitted_diff_for_buffer(
editor: Entity<Editor>,
project: &Entity<Project>,
buffers: impl IntoIterator<Item = Entity<Buffer>>,
buffer: Entity<MultiBuffer>,
@ -18891,6 +18923,13 @@ fn get_uncommitted_diff_for_buffer(
});
cx.spawn(async move |cx| {
let diffs = future::join_all(tasks).await;
if editor
.read_with(cx, |editor, _cx| editor.temporary_diff_override)
.unwrap_or(false)
{
return;
}
buffer
.update(cx, |buffer, cx| {
for diff in diffs.into_iter().flatten() {