Don't save buffer until *after* autoformatting completes.
Co-authored-by: Agus Zubiaga <hi@aguz.me>
This commit is contained in:
@@ -192,8 +192,9 @@ impl Tool for EditFileTool {
|
||||
});
|
||||
|
||||
let card_clone = card.clone();
|
||||
let action_log_clone = action_log.clone();
|
||||
let task = cx.spawn(async move |cx: &mut AsyncApp| {
|
||||
let edit_agent = EditAgent::new(model, project.clone(), action_log, Templates::new());
|
||||
let edit_agent = EditAgent::new(model, project.clone(), action_log_clone, Templates::new());
|
||||
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
@@ -254,104 +255,49 @@ impl Tool for EditFileTool {
|
||||
}
|
||||
let agent_output = output.await?;
|
||||
|
||||
// Check if format_on_save is enabled and get the language if formatting is needed
|
||||
let language_to_format = buffer
|
||||
// Check if format_on_save is enabled
|
||||
let format_on_save_enabled = buffer
|
||||
.read_with(cx, |buffer, cx| {
|
||||
let language = buffer.language().cloned();
|
||||
let settings = language_settings::language_settings(
|
||||
language.as_ref().map(|l| l.name()),
|
||||
buffer.language().map(|l| l.name()),
|
||||
buffer.file(),
|
||||
cx,
|
||||
);
|
||||
if !matches!(settings.format_on_save, FormatOnSave::Off) {
|
||||
language
|
||||
} else {
|
||||
None
|
||||
}
|
||||
!matches!(settings.format_on_save, FormatOnSave::Off)
|
||||
})
|
||||
.log_err()
|
||||
.flatten();
|
||||
.unwrap_or(false);
|
||||
|
||||
let final_text = if let Some(language) = language_to_format {
|
||||
// Format the content in a separate buffer to get the formatted text
|
||||
// without triggering "file changed" events on the actual buffer
|
||||
let unformatted_snapshot = buffer.read_with(cx, |buffer, _cx| buffer.snapshot())?;
|
||||
let unformatted_text = cx
|
||||
.background_spawn({
|
||||
let unformatted_snapshot = unformatted_snapshot.clone();
|
||||
async move { unformatted_snapshot.text() }
|
||||
})
|
||||
.await;
|
||||
|
||||
// Create a temporary buffer to apply formatting without triggering file change events
|
||||
let file = buffer.read_with(cx, |buffer, _| buffer.file().cloned())?;
|
||||
let line_ending = buffer.read_with(cx, |buffer, _| buffer.line_ending())?;
|
||||
|
||||
let temp_buffer_entity = cx.new(|cx| {
|
||||
let text_buffer = TextBuffer::new_normalized(
|
||||
0,
|
||||
cx.entity_id().as_non_zero_u64().into(),
|
||||
line_ending,
|
||||
unformatted_text.clone().into(),
|
||||
);
|
||||
let mut temp_buffer = Buffer::build(text_buffer, file, Capability::ReadWrite);
|
||||
temp_buffer.set_language(Some(language), cx);
|
||||
temp_buffer
|
||||
// If format_on_save is enabled, format the buffer before saving
|
||||
if format_on_save_enabled {
|
||||
let format_task = project.update(cx, |project, cx| {
|
||||
project.format(
|
||||
HashSet::from_iter([buffer.clone()]),
|
||||
LspFormatTarget::Buffers,
|
||||
false, // Don't push to history since the tool did it.
|
||||
FormatTrigger::Save,
|
||||
cx,
|
||||
)
|
||||
})?;
|
||||
|
||||
// Format the temporary buffer
|
||||
let buffers = HashSet::from_iter([temp_buffer_entity.clone()]);
|
||||
|
||||
if let Some(format_task) = project
|
||||
.update(cx, move |project, cx| {
|
||||
project.format(
|
||||
buffers,
|
||||
LspFormatTarget::Buffers,
|
||||
false, // Don't push to history since the tool did it.
|
||||
FormatTrigger::Save,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.log_err()
|
||||
{
|
||||
format_task.await.log_err();
|
||||
}
|
||||
|
||||
// Get the formatted text
|
||||
let formatted_text = temp_buffer_entity.read_with(cx, |buffer, _| buffer.text())?;
|
||||
|
||||
// Apply the formatted content to the actual buffer
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
let range = 0..buffer.len();
|
||||
buffer.edit([(range, formatted_text.clone())], None, cx);
|
||||
})?;
|
||||
|
||||
formatted_text
|
||||
} else {
|
||||
// No formatting needed, just get the current text
|
||||
let new_snapshot = buffer.read_with(cx, |buffer, _cx| buffer.snapshot())?;
|
||||
cx.background_spawn({
|
||||
let new_snapshot = new_snapshot.clone();
|
||||
async move { new_snapshot.text() }
|
||||
})
|
||||
.await
|
||||
};
|
||||
format_task.await.log_err();
|
||||
}
|
||||
|
||||
project
|
||||
.update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))?
|
||||
.await?;
|
||||
|
||||
// Notify the action log that we've edited the buffer AFTER save completes
|
||||
// This ensures the tracked version matches the saved version
|
||||
action_log.update(cx, |log, cx| {
|
||||
log.buffer_edited(buffer.clone(), cx);
|
||||
})?;
|
||||
|
||||
let new_snapshot = buffer.read_with(cx, |buffer, _cx| buffer.snapshot())?;
|
||||
let new_text = cx.background_spawn({
|
||||
let new_snapshot = new_snapshot.clone();
|
||||
async move { new_snapshot.text() }
|
||||
});
|
||||
let diff = cx.background_spawn({
|
||||
let old_text = old_text.clone();
|
||||
let final_text = final_text.clone();
|
||||
async move { language::unified_diff(&old_text, &final_text) }
|
||||
});
|
||||
let (new_text, diff) = futures::join!(new_text, diff);
|
||||
})
|
||||
.await;
|
||||
let diff = language::unified_diff(&old_text, &new_text);
|
||||
|
||||
let output = EditFileToolOutput {
|
||||
original_path: project_path.path.to_path_buf(),
|
||||
@@ -362,7 +308,7 @@ impl Tool for EditFileTool {
|
||||
|
||||
if let Some(card) = card_clone {
|
||||
card.update(cx, |card, cx| {
|
||||
card.set_diff(project_path.path.clone(), old_text, final_text.clone(), cx);
|
||||
card.set_diff(project_path.path.clone(), old_text, new_text.clone(), cx);
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user