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:
parent
02f9df3c7b
commit
550c3fb506
@ -194,6 +194,16 @@
|
||||
"alt-shift-y": "git::UnstageAndNext"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && editor_agent_diff",
|
||||
"bindings": {
|
||||
"ctrl-y": "agent::Keep",
|
||||
"ctrl-n": "agent::Reject",
|
||||
"ctrl-shift-y": "agent::KeepAll",
|
||||
"ctrl-shift-n": "agent::RejectAll",
|
||||
"shift-ctrl-r": "agent::OpenAgentDiff"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "AgentDiff",
|
||||
"bindings": {
|
||||
|
@ -247,6 +247,17 @@
|
||||
"cmd-shift-n": "agent::RejectAll"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && editor_agent_diff",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-y": "agent::Keep",
|
||||
"cmd-n": "agent::Reject",
|
||||
"cmd-shift-y": "agent::KeepAll",
|
||||
"cmd-shift-n": "agent::RejectAll",
|
||||
"shift-ctrl-r": "agent::OpenAgentDiff"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "AssistantPanel",
|
||||
"use_key_equivalents": true,
|
||||
|
@ -765,7 +765,6 @@ impl ActiveThread {
|
||||
.unwrap()
|
||||
}
|
||||
});
|
||||
|
||||
let mut this = Self {
|
||||
language_registry,
|
||||
thread_store,
|
||||
@ -954,6 +953,9 @@ impl ActiveThread {
|
||||
ThreadEvent::UsageUpdated(usage) => {
|
||||
self.last_usage = Some(*usage);
|
||||
}
|
||||
ThreadEvent::NewRequest | ThreadEvent::CompletionCanceled => {
|
||||
cx.notify();
|
||||
}
|
||||
ThreadEvent::StreamedCompletion
|
||||
| ThreadEvent::SummaryGenerated
|
||||
| ThreadEvent::SummaryChanged => {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -45,7 +45,7 @@ pub use crate::context::{ContextLoadResult, LoadedContext};
|
||||
pub use crate::inline_assistant::InlineAssistant;
|
||||
pub use crate::thread::{Message, MessageSegment, Thread, ThreadEvent};
|
||||
pub use crate::thread_store::ThreadStore;
|
||||
pub use agent_diff::{AgentDiff, AgentDiffToolbar};
|
||||
pub use agent_diff::{AgentDiffPane, AgentDiffToolbar};
|
||||
pub use context_store::ContextStore;
|
||||
pub use ui::{all_agent_previews, get_agent_preview};
|
||||
|
||||
|
@ -43,6 +43,7 @@ use zed_actions::agent::OpenConfiguration;
|
||||
use zed_actions::assistant::{OpenRulesLibrary, ToggleFocus};
|
||||
|
||||
use crate::active_thread::{ActiveThread, ActiveThreadEvent};
|
||||
use crate::agent_diff::AgentDiff;
|
||||
use crate::assistant_configuration::{AssistantConfiguration, AssistantConfigurationEvent};
|
||||
use crate::history_store::{HistoryEntry, HistoryStore, RecentEntry};
|
||||
use crate::message_editor::{MessageEditor, MessageEditorEvent};
|
||||
@ -51,9 +52,9 @@ use crate::thread_history::{PastContext, PastThread, ThreadHistory};
|
||||
use crate::thread_store::ThreadStore;
|
||||
use crate::ui::UsageBanner;
|
||||
use crate::{
|
||||
AddContextServer, AgentDiff, DeleteRecentlyOpenThread, ExpandMessageEditor, InlineAssistant,
|
||||
NewTextThread, NewThread, OpenActiveThreadAsMarkdown, OpenAgentDiff, OpenHistory, ThreadEvent,
|
||||
ToggleContextPicker, ToggleNavigationMenu, ToggleOptionsMenu,
|
||||
AddContextServer, AgentDiffPane, DeleteRecentlyOpenThread, ExpandMessageEditor,
|
||||
InlineAssistant, NewTextThread, NewThread, OpenActiveThreadAsMarkdown, OpenAgentDiff,
|
||||
OpenHistory, ThreadEvent, ToggleContextPicker, ToggleNavigationMenu, ToggleOptionsMenu,
|
||||
};
|
||||
|
||||
const AGENT_PANEL_KEY: &str = "agent_panel";
|
||||
@ -103,7 +104,7 @@ pub fn init(cx: &mut App) {
|
||||
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
|
||||
workspace.focus_panel::<AssistantPanel>(window, cx);
|
||||
let thread = panel.read(cx).thread.read(cx).thread().clone();
|
||||
AgentDiff::deploy_in_workspace(thread, workspace, window, cx);
|
||||
AgentDiffPane::deploy_in_workspace(thread, workspace, window, cx);
|
||||
}
|
||||
})
|
||||
.register_action(|workspace, _: &ExpandMessageEditor, window, cx| {
|
||||
@ -474,6 +475,7 @@ impl AssistantPanel {
|
||||
cx,
|
||||
)
|
||||
});
|
||||
AgentDiff::set_active_thread(&workspace, &thread, cx);
|
||||
|
||||
let active_thread_subscription =
|
||||
cx.subscribe(&active_thread, |_, _, event, cx| match &event {
|
||||
@ -717,6 +719,7 @@ impl AssistantPanel {
|
||||
cx,
|
||||
)
|
||||
});
|
||||
AgentDiff::set_active_thread(&self.workspace, &thread, cx);
|
||||
|
||||
let active_thread_subscription =
|
||||
cx.subscribe(&self.thread, |_, _, event, cx| match &event {
|
||||
@ -914,6 +917,7 @@ impl AssistantPanel {
|
||||
cx,
|
||||
)
|
||||
});
|
||||
AgentDiff::set_active_thread(&self.workspace, &thread, cx);
|
||||
|
||||
let active_thread_subscription =
|
||||
cx.subscribe(&self.thread, |_, _, event, cx| match &event {
|
||||
@ -989,7 +993,7 @@ impl AssistantPanel {
|
||||
let thread = self.thread.read(cx).thread().clone();
|
||||
self.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
AgentDiff::deploy_in_workspace(thread, workspace, window, cx)
|
||||
AgentDiffPane::deploy_in_workspace(thread, workspace, window, cx)
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
|
@ -42,8 +42,8 @@ use crate::profile_selector::ProfileSelector;
|
||||
use crate::thread::{MessageCrease, Thread, TokenUsageRatio};
|
||||
use crate::thread_store::ThreadStore;
|
||||
use crate::{
|
||||
ActiveThread, AgentDiff, Chat, ExpandMessageEditor, NewThread, OpenAgentDiff, RemoveAllContext,
|
||||
ToggleContextPicker, ToggleProfileSelector, register_agent_preview,
|
||||
ActiveThread, AgentDiffPane, Chat, ExpandMessageEditor, NewThread, OpenAgentDiff,
|
||||
RemoveAllContext, ToggleContextPicker, ToggleProfileSelector, register_agent_preview,
|
||||
};
|
||||
|
||||
#[derive(RegisterComponent)]
|
||||
@ -168,6 +168,9 @@ impl MessageEditor {
|
||||
// When context changes, reload it for token counting.
|
||||
let _ = this.reload_context(cx);
|
||||
}),
|
||||
cx.observe(&thread.read(cx).action_log().clone(), |_, _, cx| {
|
||||
cx.notify()
|
||||
}),
|
||||
];
|
||||
|
||||
let model_selector = cx.new(|cx| {
|
||||
@ -404,7 +407,7 @@ impl MessageEditor {
|
||||
|
||||
fn handle_review_click(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.edits_expanded = true;
|
||||
AgentDiff::deploy(self.thread.clone(), self.workspace.clone(), window, cx).log_err();
|
||||
AgentDiffPane::deploy(self.thread.clone(), self.workspace.clone(), window, cx).log_err();
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
@ -414,7 +417,8 @@ impl MessageEditor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Ok(diff) = AgentDiff::deploy(self.thread.clone(), self.workspace.clone(), window, cx)
|
||||
if let Ok(diff) =
|
||||
AgentDiffPane::deploy(self.thread.clone(), self.workspace.clone(), window, cx)
|
||||
{
|
||||
let path_key = multi_buffer::PathKey::for_buffer(&buffer, cx);
|
||||
diff.update(cx, |diff, cx| diff.move_to_path(path_key, window, cx));
|
||||
|
@ -1325,13 +1325,14 @@ impl Thread {
|
||||
let mut stop_reason = StopReason::EndTurn;
|
||||
let mut current_token_usage = TokenUsage::default();
|
||||
|
||||
if let Some(usage) = usage {
|
||||
thread
|
||||
.update(cx, |_thread, cx| {
|
||||
thread
|
||||
.update(cx, |_thread, cx| {
|
||||
if let Some(usage) = usage {
|
||||
cx.emit(ThreadEvent::UsageUpdated(usage));
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
cx.emit(ThreadEvent::NewRequest);
|
||||
})
|
||||
.ok();
|
||||
|
||||
let mut request_assistant_message_id = None;
|
||||
|
||||
@ -1962,6 +1963,11 @@ impl Thread {
|
||||
}
|
||||
|
||||
self.finalize_pending_checkpoint(cx);
|
||||
|
||||
if canceled {
|
||||
cx.emit(ThreadEvent::CompletionCanceled);
|
||||
}
|
||||
|
||||
canceled
|
||||
}
|
||||
|
||||
@ -2463,6 +2469,7 @@ pub enum ThreadEvent {
|
||||
UsageUpdated(RequestUsage),
|
||||
StreamedCompletion,
|
||||
ReceivedTextChunk,
|
||||
NewRequest,
|
||||
StreamedAssistantText(MessageId, String),
|
||||
StreamedAssistantThinking(MessageId, String),
|
||||
StreamedToolUse {
|
||||
@ -2493,6 +2500,7 @@ pub enum ThreadEvent {
|
||||
CheckpointChanged,
|
||||
ToolConfirmationNeeded,
|
||||
CancelEditing,
|
||||
CompletionCanceled,
|
||||
}
|
||||
|
||||
impl EventEmitter<ThreadEvent> for Thread {}
|
||||
|
@ -69,7 +69,7 @@ pub enum AssistantProviderContentV1 {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AssistantSettings {
|
||||
pub enabled: bool,
|
||||
pub button: bool,
|
||||
@ -88,6 +88,32 @@ pub struct AssistantSettings {
|
||||
pub always_allow_tool_actions: bool,
|
||||
pub notify_when_agent_waiting: NotifyWhenAgentWaiting,
|
||||
pub stream_edits: bool,
|
||||
pub single_file_review: bool,
|
||||
}
|
||||
|
||||
impl Default for AssistantSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: Default::default(),
|
||||
button: Default::default(),
|
||||
dock: Default::default(),
|
||||
default_width: Default::default(),
|
||||
default_height: Default::default(),
|
||||
default_model: Default::default(),
|
||||
inline_assistant_model: Default::default(),
|
||||
commit_message_model: Default::default(),
|
||||
thread_summary_model: Default::default(),
|
||||
inline_alternatives: Default::default(),
|
||||
using_outdated_settings_version: Default::default(),
|
||||
enable_experimental_live_diffs: Default::default(),
|
||||
default_profile: Default::default(),
|
||||
profiles: Default::default(),
|
||||
always_allow_tool_actions: Default::default(),
|
||||
notify_when_agent_waiting: Default::default(),
|
||||
stream_edits: Default::default(),
|
||||
single_file_review: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AssistantSettings {
|
||||
@ -224,6 +250,7 @@ impl AssistantSettingsContent {
|
||||
always_allow_tool_actions: None,
|
||||
notify_when_agent_waiting: None,
|
||||
stream_edits: None,
|
||||
single_file_review: None,
|
||||
},
|
||||
VersionedAssistantSettingsContent::V2(ref settings) => settings.clone(),
|
||||
},
|
||||
@ -252,6 +279,7 @@ impl AssistantSettingsContent {
|
||||
always_allow_tool_actions: None,
|
||||
notify_when_agent_waiting: None,
|
||||
stream_edits: None,
|
||||
single_file_review: None,
|
||||
},
|
||||
None => AssistantSettingsContentV2::default(),
|
||||
}
|
||||
@ -503,6 +531,7 @@ impl Default for VersionedAssistantSettingsContent {
|
||||
always_allow_tool_actions: None,
|
||||
notify_when_agent_waiting: None,
|
||||
stream_edits: None,
|
||||
single_file_review: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -562,6 +591,10 @@ pub struct AssistantSettingsContentV2 {
|
||||
///
|
||||
/// Default: false
|
||||
stream_edits: Option<bool>,
|
||||
/// Whether to display agent edits in single-file editors in addition to the review multibuffer pane.
|
||||
///
|
||||
/// Default: true
|
||||
single_file_review: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
@ -725,6 +758,7 @@ impl Settings for AssistantSettings {
|
||||
value.notify_when_agent_waiting,
|
||||
);
|
||||
merge(&mut settings.stream_edits, value.stream_edits);
|
||||
merge(&mut settings.single_file_review, value.single_file_review);
|
||||
merge(&mut settings.default_profile, value.default_profile);
|
||||
|
||||
if let Some(profiles) = value.profiles {
|
||||
@ -857,6 +891,7 @@ mod tests {
|
||||
always_allow_tool_actions: None,
|
||||
notify_when_agent_waiting: None,
|
||||
stream_edits: None,
|
||||
single_file_review: None,
|
||||
},
|
||||
)),
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -234,9 +234,11 @@ impl ExampleContext {
|
||||
tx.try_send(Err(anyhow!(err.clone()))).ok();
|
||||
}
|
||||
},
|
||||
ThreadEvent::StreamedAssistantText(_, _)
|
||||
ThreadEvent::NewRequest
|
||||
| ThreadEvent::StreamedAssistantText(_, _)
|
||||
| ThreadEvent::StreamedAssistantThinking(_, _)
|
||||
| ThreadEvent::UsePendingTools { .. } => {}
|
||||
| ThreadEvent::UsePendingTools { .. }
|
||||
| ThreadEvent::CompletionCanceled => {}
|
||||
ThreadEvent::ToolFinished {
|
||||
tool_use_id,
|
||||
pending_tool_use,
|
||||
|
@ -2633,6 +2633,11 @@ impl MultiBuffer {
|
||||
self.snapshot.borrow().all_diff_hunks_expanded
|
||||
}
|
||||
|
||||
pub fn set_all_diff_hunks_collapsed(&mut self, cx: &mut Context<Self>) {
|
||||
self.snapshot.borrow_mut().all_diff_hunks_expanded = false;
|
||||
self.expand_or_collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], false, cx);
|
||||
}
|
||||
|
||||
pub fn has_multiple_hunks(&self, cx: &App) -> bool {
|
||||
self.read(cx)
|
||||
.diff_hunks_in_range(Anchor::min()..Anchor::max())
|
||||
|
@ -111,6 +111,7 @@ impl Render for Toolbar {
|
||||
})
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.relative()
|
||||
.bg(cx.theme().colors().toolbar_background)
|
||||
.when(has_left_items || has_right_items, |this| {
|
||||
this.child(
|
||||
|
Loading…
x
Reference in New Issue
Block a user