@@ -47,7 +47,7 @@ use std::{
time::{Duration, Instant},
};
use thiserror::Error;
-use util::{ResultExt as _, debug_panic, post_inc};
+use util::{ResultExt as _, post_inc};
use uuid::Uuid;
use zed_llm_client::{CompletionIntent, CompletionRequestStatus, UsageLimit};
@@ -1582,23 +1582,21 @@ impl Thread {
model: Arc<dyn LanguageModel>,
cx: &mut App,
) -> Option<PendingToolUse> {
- let action_log = self.action_log.read(cx);
-
- if !action_log.has_unnotified_user_edits() {
- return None;
- }
-
// Represent notification as a simulated `project_notifications` tool call
let tool_name = Arc::from("project_notifications");
- let Some(tool) = self.tools.read(cx).tool(&tool_name, cx) else {
- debug_panic!("`project_notifications` tool not found");
- return None;
- };
+ let tool = self.tools.read(cx).tool(&tool_name, cx)?;
if !self.profile.is_tool_enabled(tool.source(), tool.name(), cx) {
return None;
}
+ if self
+ .action_log
+ .update(cx, |log, cx| log.unnotified_user_edits(cx).is_none())
+ {
+ return None;
+ }
+
let input = serde_json::json!({});
let request = Arc::new(LanguageModelRequest::default()); // unused
let window = None;
@@ -51,23 +51,13 @@ impl ActionLog {
Some(self.tracked_buffers.get(buffer)?.snapshot.clone())
}
- pub fn has_unnotified_user_edits(&self) -> bool {
- self.tracked_buffers
- .values()
- .any(|tracked| tracked.has_unnotified_user_edits)
- }
-
/// Return a unified diff patch with user edits made since last read or notification
pub fn unnotified_user_edits(&self, cx: &Context<Self>) -> Option<String> {
- if !self.has_unnotified_user_edits() {
- return None;
- }
-
- let unified_diff = self
+ let diffs = self
.tracked_buffers
.values()
.filter_map(|tracked| {
- if !tracked.has_unnotified_user_edits {
+ if !tracked.may_have_unnotified_user_edits {
return None;
}
@@ -95,9 +85,13 @@ impl ActionLog {
Some(result)
})
- .collect::<Vec<_>>()
- .join("\n\n");
+ .collect::<Vec<_>>();
+
+ if diffs.is_empty() {
+ return None;
+ }
+ let unified_diff = diffs.join("\n\n");
Some(unified_diff)
}
@@ -106,7 +100,7 @@ impl ActionLog {
pub fn flush_unnotified_user_edits(&mut self, cx: &Context<Self>) -> Option<String> {
let patch = self.unnotified_user_edits(cx);
self.tracked_buffers.values_mut().for_each(|tracked| {
- tracked.has_unnotified_user_edits = false;
+ tracked.may_have_unnotified_user_edits = false;
tracked.last_seen_base = tracked.diff_base.clone();
});
patch
@@ -185,7 +179,7 @@ impl ActionLog {
version: buffer.read(cx).version(),
diff,
diff_update: diff_update_tx,
- has_unnotified_user_edits: false,
+ may_have_unnotified_user_edits: false,
_open_lsp_handle: open_lsp_handle,
_maintain_diff: cx.spawn({
let buffer = buffer.clone();
@@ -337,27 +331,34 @@ impl ActionLog {
let new_snapshot = buffer_snapshot.clone();
let unreviewed_edits = tracked_buffer.unreviewed_edits.clone();
let edits = diff_snapshots(&old_snapshot, &new_snapshot);
- if let ChangeAuthor::User = author
- && !edits.is_empty()
- {
- tracked_buffer.has_unnotified_user_edits = true;
- }
+ let mut has_user_changes = false;
async move {
if let ChangeAuthor::User = author {
- apply_non_conflicting_edits(
+ has_user_changes = apply_non_conflicting_edits(
&unreviewed_edits,
edits,
&mut base_text,
new_snapshot.as_rope(),
);
}
- (Arc::new(base_text.to_string()), base_text)
+
+ (Arc::new(base_text.to_string()), base_text, has_user_changes)
}
});
anyhow::Ok(rebase)
})??;
- let (new_base_text, new_diff_base) = rebase.await;
+ let (new_base_text, new_diff_base, has_user_changes) = rebase.await;
+
+ this.update(cx, |this, _| {
+ let tracked_buffer = this
+ .tracked_buffers
+ .get_mut(buffer)
+ .context("buffer not tracked")
+ .unwrap();
+ tracked_buffer.may_have_unnotified_user_edits |= has_user_changes;
+ })?;
+
Self::update_diff(
this,
buffer,
@@ -829,11 +830,12 @@ fn apply_non_conflicting_edits(
edits: Vec<Edit<u32>>,
old_text: &mut Rope,
new_text: &Rope,
-) {
+) -> bool {
let mut old_edits = patch.edits().iter().cloned().peekable();
let mut new_edits = edits.into_iter().peekable();
let mut applied_delta = 0i32;
let mut rebased_delta = 0i32;
+ let mut has_made_changes = false;
while let Some(mut new_edit) = new_edits.next() {
let mut conflict = false;
@@ -883,8 +885,10 @@ fn apply_non_conflicting_edits(
&new_text.chunks_in_range(new_bytes).collect::<String>(),
);
applied_delta += new_edit.new_len() as i32 - new_edit.old_len() as i32;
+ has_made_changes = true;
}
}
+ has_made_changes
}
fn diff_snapshots(
@@ -958,7 +962,7 @@ struct TrackedBuffer {
diff: Entity<BufferDiff>,
snapshot: text::BufferSnapshot,
diff_update: mpsc::UnboundedSender<(ChangeAuthor, text::BufferSnapshot)>,
- has_unnotified_user_edits: bool,
+ may_have_unnotified_user_edits: bool,
_open_lsp_handle: OpenLspBufferHandle,
_maintain_diff: Task<()>,
_subscription: Subscription,
@@ -278,6 +278,9 @@ impl Tool for EditFileTool {
.unwrap_or(false);
if format_on_save_enabled {
+ action_log.update(cx, |log, cx| {
+ log.buffer_edited(buffer.clone(), cx);
+ })?;
let format_task = project.update(cx, |project, cx| {
project.format(
HashSet::from_iter([buffer.clone()]),