From e0a2561e143d416c3ed12b8efd873baea72b26df Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 9 Feb 2026 08:57:26 -0500 Subject: [PATCH] Add format-on-save support to streaming edit file tool (#48663) When saving after a streaming edit, check if format-on-save is enabled for the buffer's language and run formatting before saving. (No release notes because we aren't using the streaming edit tool yet.) Release Notes: - N/A --- .../src/tools/streaming_edit_file_tool.rs | 58 +++++++++++++++++-- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/crates/agent/src/tools/streaming_edit_file_tool.rs b/crates/agent/src/tools/streaming_edit_file_tool.rs index bbdc05c05bca13c582f16da61296e2cb3001a48e..717761038a37f2b2089b5547d5daab411fb38a5e 100644 --- a/crates/agent/src/tools/streaming_edit_file_tool.rs +++ b/crates/agent/src/tools/streaming_edit_file_tool.rs @@ -8,9 +8,13 @@ use crate::{ use acp_thread::Diff; use agent_client_protocol::{self as acp, ToolCallLocation, ToolCallUpdateFields}; use anyhow::{Context as _, Result, anyhow}; +use collections::HashSet; +use futures::FutureExt as _; use gpui::{App, AppContext, AsyncApp, Entity, Task, WeakEntity}; use language::LanguageRegistry; +use language::language_settings::{self, FormatOnSave}; use language_model::LanguageModelToolResultContent; +use project::lsp_store::{FormatTrigger, LspFormatTarget}; use project::{Project, ProjectPath}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -19,6 +23,7 @@ use std::path::PathBuf; use std::sync::Arc; use text::BufferSnapshot; use ui::SharedString; +use util::ResultExt; use util::rel_path::RelPath; const DEFAULT_UI_TEXT: &str = "Editing file"; @@ -372,9 +377,45 @@ impl AgentTool for StreamingEditFileTool { } } - project - .update(cx, |project, cx| project.save_buffer(buffer.clone(), cx)) - .await?; + let format_on_save_enabled = buffer.read_with(cx, |buffer, cx| { + let settings = language_settings::language_settings( + buffer.language().map(|l| l.name()), + buffer.file(), + cx, + ); + settings.format_on_save != FormatOnSave::Off + }); + + 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()]), + LspFormatTarget::Buffers, + false, + FormatTrigger::Save, + cx, + ) + }); + futures::select! { + result = format_task.fuse() => { result.log_err(); }, + _ = event_stream.cancelled_by_user().fuse() => { + anyhow::bail!("Edit cancelled by user"); + } + }; + } + + let save_task = project + .update(cx, |project, cx| project.save_buffer(buffer.clone(), cx)); + futures::select! { + result = save_task.fuse() => { result?; }, + _ = event_stream.cancelled_by_user().fuse() => { + anyhow::bail!("Edit cancelled by user"); + } + }; action_log.update(cx, |log, cx| { log.buffer_edited(buffer.clone(), cx); @@ -657,7 +698,7 @@ fn resolve_path( mod tests { use super::*; use crate::{ContextServerRegistry, Templates}; - use gpui::TestAppContext; + use gpui::{TestAppContext, UpdateGlobal}; use language_model::fake_provider::FakeLanguageModel; use prompt_store::ProjectContext; use serde_json::json; @@ -1183,6 +1224,15 @@ mod tests { cx.update(|cx| { let settings_store = SettingsStore::test(cx); cx.set_global(settings_store); + SettingsStore::update_global(cx, |store: &mut SettingsStore, cx| { + store.update_user_settings(cx, |settings| { + settings + .project + .all_languages + .defaults + .ensure_final_newline_on_save = Some(false); + }); + }); }); } }