Clean up formatting code and add testing for formatting with multiple formatters (including code actions!) (#28457)

Ben Kunkle and Max Brunsfeld created

Release Notes:

- N/A

---------

Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>

Change summary

crates/editor/src/editor_tests.rs |  444 +++++++++--
crates/project/src/lsp_store.rs   | 1295 ++++++++++++++------------------
2 files changed, 919 insertions(+), 820 deletions(-)

Detailed changes

crates/editor/src/editor_tests.rs 🔗

@@ -7756,77 +7756,81 @@ async fn test_document_format_during_save(cx: &mut TestAppContext) {
     cx.executor().start_waiting();
     let fake_server = fake_servers.next().await.unwrap();
 
-    let save = editor
-        .update_in(cx, |editor, window, cx| {
-            editor.save(true, project.clone(), window, cx)
-        })
-        .unwrap();
-    fake_server
-        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
-            assert_eq!(
-                params.text_document.uri,
-                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
-            );
-            assert_eq!(params.options.tab_size, 4);
-            Ok(Some(vec![lsp::TextEdit::new(
-                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
-                ", ".to_string(),
-            )]))
-        })
-        .next()
-        .await;
-    cx.executor().start_waiting();
-    save.await;
+    {
+        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
+            move |params, _| async move {
+                assert_eq!(
+                    params.text_document.uri,
+                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
+                );
+                assert_eq!(params.options.tab_size, 4);
+                Ok(Some(vec![lsp::TextEdit::new(
+                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
+                    ", ".to_string(),
+                )]))
+            },
+        );
+        let save = editor
+            .update_in(cx, |editor, window, cx| {
+                editor.save(true, project.clone(), window, cx)
+            })
+            .unwrap();
+        cx.executor().start_waiting();
+        save.await;
 
-    assert_eq!(
-        editor.update(cx, |editor, cx| editor.text(cx)),
-        "one, two\nthree\n"
-    );
-    assert!(!cx.read(|cx| editor.is_dirty(cx)));
+        assert_eq!(
+            editor.update(cx, |editor, cx| editor.text(cx)),
+            "one, two\nthree\n"
+        );
+        assert!(!cx.read(|cx| editor.is_dirty(cx)));
+    }
 
-    editor.update_in(cx, |editor, window, cx| {
-        editor.set_text("one\ntwo\nthree\n", window, cx)
-    });
-    assert!(cx.read(|cx| editor.is_dirty(cx)));
+    {
+        editor.update_in(cx, |editor, window, cx| {
+            editor.set_text("one\ntwo\nthree\n", window, cx)
+        });
+        assert!(cx.read(|cx| editor.is_dirty(cx)));
 
-    // Ensure we can still save even if formatting hangs.
-    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
-        move |params, _| async move {
-            assert_eq!(
-                params.text_document.uri,
-                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
-            );
-            futures::future::pending::<()>().await;
-            unreachable!()
-        },
-    );
-    let save = editor
-        .update_in(cx, |editor, window, cx| {
-            editor.save(true, project.clone(), window, cx)
-        })
-        .unwrap();
-    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
-    cx.executor().start_waiting();
-    save.await;
-    assert_eq!(
-        editor.update(cx, |editor, cx| editor.text(cx)),
-        "one\ntwo\nthree\n"
-    );
-    assert!(!cx.read(|cx| editor.is_dirty(cx)));
+        // Ensure we can still save even if formatting hangs.
+        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
+            move |params, _| async move {
+                assert_eq!(
+                    params.text_document.uri,
+                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
+                );
+                futures::future::pending::<()>().await;
+                unreachable!()
+            },
+        );
+        let save = editor
+            .update_in(cx, |editor, window, cx| {
+                editor.save(true, project.clone(), window, cx)
+            })
+            .unwrap();
+        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
+        cx.executor().start_waiting();
+        save.await;
+        assert_eq!(
+            editor.update(cx, |editor, cx| editor.text(cx)),
+            "one\ntwo\nthree\n"
+        );
+    }
 
     // For non-dirty buffer, no formatting request should be sent
-    let save = editor
-        .update_in(cx, |editor, window, cx| {
-            editor.save(true, project.clone(), window, cx)
-        })
-        .unwrap();
-    let _pending_format_request = fake_server
-        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
+    {
+        assert!(!cx.read(|cx| editor.is_dirty(cx)));
+
+        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
             panic!("Should not be invoked on non-dirty buffer");
-        })
-        .next();
-    cx.executor().start_waiting();
-    save.await;
+        });
+        let save = editor
+            .update_in(cx, |editor, window, cx| {
+                editor.save(true, project.clone(), window, cx)
+            })
+            .unwrap();
+        cx.executor().start_waiting();
+        save.await;
+    }
 
     // Set rust language override and assert overridden tabsize is sent to language server
     update_test_language_settings(cx, |settings| {
@@ -7839,28 +7843,28 @@ async fn test_document_format_during_save(cx: &mut TestAppContext) {
         );
     });
 
-    editor.update_in(cx, |editor, window, cx| {
-        editor.set_text("somehting_new\n", window, cx)
-    });
-    assert!(cx.read(|cx| editor.is_dirty(cx)));
-    let save = editor
-        .update_in(cx, |editor, window, cx| {
-            editor.save(true, project.clone(), window, cx)
-        })
-        .unwrap();
-    fake_server
-        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
-            assert_eq!(
-                params.text_document.uri,
-                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
-            );
-            assert_eq!(params.options.tab_size, 8);
-            Ok(Some(vec![]))
-        })
-        .next()
-        .await;
-    cx.executor().start_waiting();
-    save.await;
+    {
+        editor.update_in(cx, |editor, window, cx| {
+            editor.set_text("somehting_new\n", window, cx)
+        });
+        assert!(cx.read(|cx| editor.is_dirty(cx)));
+        let _formatting_request_signal = fake_server
+            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
+                assert_eq!(
+                    params.text_document.uri,
+                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
+                );
+                assert_eq!(params.options.tab_size, 8);
+                Ok(Some(vec![]))
+            });
+        let save = editor
+            .update_in(cx, |editor, window, cx| {
+                editor.save(true, project.clone(), window, cx)
+            })
+            .unwrap();
+        cx.executor().start_waiting();
+        save.await;
+    }
 }
 
 #[gpui::test]
@@ -8342,6 +8346,272 @@ async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
     );
 }
 
+#[gpui::test]
+async fn test_multiple_formatters(cx: &mut TestAppContext) {
+    init_test(cx, |settings| {
+        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
+        settings.defaults.formatter =
+            Some(language_settings::SelectedFormatter::List(FormatterList(
+                vec![
+                    Formatter::LanguageServer { name: None },
+                    Formatter::CodeActions(
+                        [
+                            ("code-action-1".into(), true),
+                            ("code-action-2".into(), true),
+                        ]
+                        .into_iter()
+                        .collect(),
+                    ),
+                ]
+                .into(),
+            )))
+    });
+
+    let fs = FakeFs::new(cx.executor());
+    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
+        .await;
+
+    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
+    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
+    language_registry.add(rust_lang());
+
+    let mut fake_servers = language_registry.register_fake_lsp(
+        "Rust",
+        FakeLspAdapter {
+            capabilities: lsp::ServerCapabilities {
+                document_formatting_provider: Some(lsp::OneOf::Left(true)),
+                execute_command_provider: Some(lsp::ExecuteCommandOptions {
+                    commands: vec!["the-command-for-code-action-1".into()],
+                    ..Default::default()
+                }),
+                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
+                ..Default::default()
+            },
+            ..Default::default()
+        },
+    );
+
+    let buffer = project
+        .update(cx, |project, cx| {
+            project.open_local_buffer(path!("/file.rs"), cx)
+        })
+        .await
+        .unwrap();
+
+    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
+    let (editor, cx) = cx.add_window_view(|window, cx| {
+        build_editor_with_project(project.clone(), buffer, window, cx)
+    });
+
+    cx.executor().start_waiting();
+
+    let fake_server = fake_servers.next().await.unwrap();
+    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
+        move |_params, _| async move {
+            Ok(Some(vec![lsp::TextEdit::new(
+                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
+                "applied-formatting\n".to_string(),
+            )]))
+        },
+    );
+    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
+        move |params, _| async move {
+            assert_eq!(
+                params.context.only,
+                Some(vec!["code-action-1".into(), "code-action-2".into()])
+            );
+            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
+            Ok(Some(vec![
+                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
+                    kind: Some("code-action-1".into()),
+                    edit: Some(lsp::WorkspaceEdit::new(
+                        [(
+                            uri.clone(),
+                            vec![lsp::TextEdit::new(
+                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
+                                "applied-code-action-1-edit\n".to_string(),
+                            )],
+                        )]
+                        .into_iter()
+                        .collect(),
+                    )),
+                    command: Some(lsp::Command {
+                        command: "the-command-for-code-action-1".into(),
+                        ..Default::default()
+                    }),
+                    ..Default::default()
+                }),
+                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
+                    kind: Some("code-action-2".into()),
+                    edit: Some(lsp::WorkspaceEdit::new(
+                        [(
+                            uri.clone(),
+                            vec![lsp::TextEdit::new(
+                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
+                                "applied-code-action-2-edit\n".to_string(),
+                            )],
+                        )]
+                        .into_iter()
+                        .collect(),
+                    )),
+                    ..Default::default()
+                }),
+            ]))
+        },
+    );
+
+    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
+        move |params, _| async move { Ok(params) }
+    });
+
+    let command_lock = Arc::new(futures::lock::Mutex::new(()));
+    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
+        let fake = fake_server.clone();
+        let lock = command_lock.clone();
+        move |params, _| {
+            assert_eq!(params.command, "the-command-for-code-action-1");
+            let fake = fake.clone();
+            let lock = lock.clone();
+            async move {
+                lock.lock().await;
+                fake.server
+                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
+                        label: None,
+                        edit: lsp::WorkspaceEdit {
+                            changes: Some(
+                                [(
+                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
+                                    vec![lsp::TextEdit {
+                                        range: lsp::Range::new(
+                                            lsp::Position::new(0, 0),
+                                            lsp::Position::new(0, 0),
+                                        ),
+                                        new_text: "applied-code-action-1-command\n".into(),
+                                    }],
+                                )]
+                                .into_iter()
+                                .collect(),
+                            ),
+                            ..Default::default()
+                        },
+                    })
+                    .await
+                    .unwrap();
+                Ok(Some(json!(null)))
+            }
+        }
+    });
+
+    cx.executor().start_waiting();
+    editor
+        .update_in(cx, |editor, window, cx| {
+            editor.perform_format(
+                project.clone(),
+                FormatTrigger::Manual,
+                FormatTarget::Buffers,
+                window,
+                cx,
+            )
+        })
+        .unwrap()
+        .await;
+    editor.update(cx, |editor, cx| {
+        assert_eq!(
+            editor.text(cx),
+            r#"
+                applied-code-action-2-edit
+                applied-code-action-1-command
+                applied-code-action-1-edit
+                applied-formatting
+                one
+                two
+                three
+            "#
+            .unindent()
+        );
+    });
+
+    editor.update_in(cx, |editor, window, cx| {
+        editor.undo(&Default::default(), window, cx);
+        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
+    });
+
+    // Perform a manual edit while waiting for an LSP command
+    // that's being run as part of a formatting code action.
+    let lock_guard = command_lock.lock().await;
+    let format = editor
+        .update_in(cx, |editor, window, cx| {
+            editor.perform_format(
+                project.clone(),
+                FormatTrigger::Manual,
+                FormatTarget::Buffers,
+                window,
+                cx,
+            )
+        })
+        .unwrap();
+    cx.run_until_parked();
+    editor.update(cx, |editor, cx| {
+        assert_eq!(
+            editor.text(cx),
+            r#"
+                applied-code-action-1-edit
+                applied-formatting
+                one
+                two
+                three
+            "#
+            .unindent()
+        );
+
+        editor.buffer.update(cx, |buffer, cx| {
+            let ix = buffer.len(cx);
+            buffer.edit([(ix..ix, "edited\n")], None, cx);
+        });
+    });
+
+    // Allow the LSP command to proceed. Because the buffer was edited,
+    // the second code action will not be run.
+    drop(lock_guard);
+    format.await;
+    editor.update_in(cx, |editor, window, cx| {
+        assert_eq!(
+            editor.text(cx),
+            r#"
+                applied-code-action-1-command
+                applied-code-action-1-edit
+                applied-formatting
+                one
+                two
+                three
+                edited
+            "#
+            .unindent()
+        );
+
+        // The manual edit is undone first, because it is the last thing the user did
+        // (even though the command completed afterwards).
+        editor.undo(&Default::default(), window, cx);
+        assert_eq!(
+            editor.text(cx),
+            r#"
+                applied-code-action-1-command
+                applied-code-action-1-edit
+                applied-formatting
+                one
+                two
+                three
+            "#
+            .unindent()
+        );
+
+        // All the formatting (including the command, which completed after the manual edit)
+        // is undone together.
+        editor.undo(&Default::default(), window, cx);
+        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
+    });
+}
+
 #[gpui::test]
 async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
     init_test(cx, |settings| {

crates/project/src/lsp_store.rs 🔗

@@ -1082,17 +1082,6 @@ impl LocalLspStore {
             })
     }
 
-    fn primary_language_server_for_buffer<'a>(
-        &'a self,
-        buffer: &'a Buffer,
-        cx: &'a mut App,
-    ) -> Option<(&'a Arc<CachedLspAdapter>, &'a Arc<LanguageServer>)> {
-        // The list of language servers is ordered based on the `language_servers` setting
-        // for each language, thus we can consider the first one in the list to be the
-        // primary one.
-        self.language_servers_for_buffer(buffer, cx).next()
-    }
-
     async fn execute_code_action_kind_locally(
         lsp_store: WeakEntity<LspStore>,
         mut buffers: Vec<Entity<Buffer>>,
@@ -1198,816 +1187,656 @@ impl LocalLspStore {
         let mut project_transaction = ProjectTransaction::default();
 
         for buffer in &buffers {
-            let adapters_and_servers = lsp_store.update(cx, |lsp_store, cx| {
-                buffer.handle.update(cx, |buffer, cx| {
-                    lsp_store
-                        .as_local()
-                        .unwrap()
-                        .language_servers_for_buffer(buffer, cx)
-                        .map(|(adapter, lsp)| (adapter.clone(), lsp.clone()))
-                        .collect::<Vec<_>>()
-                })
-            })?;
+            // Create an empty transaction to hold all of the formatting edits.
+            let formatting_transaction_id = buffer.handle.update(cx, |buffer, cx| {
+                // ensure no transactions created while formatting are
+                // grouped with the previous transaction in the history
+                // based on the transaction group interval
+                buffer.finalize_last_transaction();
+                let transaction_id = buffer
+                    .start_transaction()
+                    .ok_or_else(|| anyhow!("transaction already open"))?;
+                let transaction = buffer
+                    .get_transaction(transaction_id)
+                    .expect("transaction started")
+                    .clone();
+                buffer.end_transaction(cx);
+                buffer.push_transaction(transaction, cx.background_executor().now());
+                buffer.finalize_last_transaction();
+                anyhow::Ok(transaction_id)
+            })??;
+
+            let result = Self::format_buffer_locally(
+                lsp_store.clone(),
+                buffer,
+                formatting_transaction_id,
+                trigger,
+                logger,
+                cx,
+            )
+            .await;
 
-            let settings = buffer.handle.read_with(cx, |buffer, cx| {
-                language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
-                    .into_owned()
+            buffer.handle.update(cx, |buffer, cx| {
+                let Some(formatting_transaction) =
+                    buffer.get_transaction(formatting_transaction_id).cloned()
+                else {
+                    zlog::warn!(logger => "no formatting transaction");
+                    return;
+                };
+                if formatting_transaction.edit_ids.is_empty() {
+                    buffer.forget_transaction(formatting_transaction_id);
+                    return;
+                }
+                if !push_to_history {
+                    zlog::trace!(logger => "forgetting format transaction");
+                    buffer.forget_transaction(formatting_transaction.id);
+                }
+                project_transaction
+                    .0
+                    .insert(cx.entity(), formatting_transaction);
             })?;
 
-            let mut transaction_id_format = None;
+            result?;
+        }
 
-            // ensure no transactions created while formatting are
-            // grouped with the previous transaction in the history
-            // based on the transaction group interval
-            buffer.handle.update(cx, |buffer, _| {
-                buffer.finalize_last_transaction();
-            })?;
+        Ok(project_transaction)
+    }
 
-            // helper function to avoid duplicate logic between formatter handlers below
-            // We want to avoid continuing to format the buffer if it has been edited since the start
-            // so we check that the last transaction id on the undo stack matches the one we expect
-            // This check should be done after each "gather" step where we generate a diff or edits to apply,
-            // and before applying them to the buffer to avoid messing up the user's buffer
-            fn err_if_buffer_edited_since_start(
-                buffer: &FormattableBuffer,
-                transaction_id_format: Option<text::TransactionId>,
-                cx: &AsyncApp,
-            ) -> Option<anyhow::Error> {
-                let transaction_id_last = buffer
-                    .handle
-                    .read_with(cx, |buffer, _| {
-                        buffer.peek_undo_stack().map(|t| t.transaction_id())
-                    })
-                    .ok()
-                    .flatten();
-                let should_continue_formatting = match (transaction_id_format, transaction_id_last)
-                {
-                    (Some(format), Some(last)) => format == last,
-                    (Some(_), None) => false,
-                    (_, _) => true,
-                };
-                if !should_continue_formatting {
-                    return Some(anyhow::anyhow!("Buffer edited while formatting. Aborting"));
+    async fn format_buffer_locally(
+        lsp_store: WeakEntity<LspStore>,
+        buffer: &FormattableBuffer,
+        formatting_transaction_id: clock::Lamport,
+        trigger: FormatTrigger,
+        logger: zlog::Logger,
+        cx: &mut AsyncApp,
+    ) -> Result<()> {
+        let (adapters_and_servers, settings) = lsp_store.update(cx, |lsp_store, cx| {
+            buffer.handle.update(cx, |buffer, cx| {
+                let adapters_and_servers = lsp_store
+                    .as_local()
+                    .unwrap()
+                    .language_servers_for_buffer(buffer, cx)
+                    .map(|(adapter, lsp)| (adapter.clone(), lsp.clone()))
+                    .collect::<Vec<_>>();
+                let settings =
+                    language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
+                        .into_owned();
+                (adapters_and_servers, settings)
+            })
+        })?;
+
+        // Apply edits to the buffer that will become part of the formatting transaction.
+        // Fails if the buffer has been edited since the start of that transaction.
+        fn extend_formatting_transaction(
+            buffer: &FormattableBuffer,
+            formatting_transaction_id: text::TransactionId,
+            cx: &mut AsyncApp,
+            operation: impl FnOnce(&mut Buffer, &mut Context<Buffer>),
+        ) -> anyhow::Result<()> {
+            buffer.handle.update(cx, |buffer, cx| {
+                let last_transaction_id = buffer.peek_undo_stack().map(|t| t.transaction_id());
+                if last_transaction_id != Some(formatting_transaction_id) {
+                    anyhow::bail!("Buffer edited while formatting. Aborting")
                 }
-                return None;
-            }
-
-            // variable used to track errors that occur during the formatting process below,
-            // but that need to not be returned right away (with `?` for example) because we
-            // still need to clean up the transaction history and update the project transaction
-            // at the very end
-            let mut result = anyhow::Ok(());
-
-            // see handling of code action formatting for why there might already be transactions
-            // in project_transaction for this buffer
-            if let Some(transaction_existing) = project_transaction.0.remove(&buffer.handle) {
-                transaction_id_format = Some(transaction_existing.id);
-                buffer.handle.update(cx, |buffer, _| {
-                    // ensure the transaction is in the history so we can group with it
-                    if buffer.get_transaction(transaction_existing.id).is_none() {
-                        buffer.push_transaction(transaction_existing, Instant::now());
-                    }
-                })?;
-            }
+                buffer.start_transaction();
+                operation(buffer, cx);
+                if let Some(transaction_id) = buffer.end_transaction(cx) {
+                    buffer.merge_transactions(transaction_id, formatting_transaction_id);
+                }
+                Ok(())
+            })?
+        }
 
-            if let Some(err) = err_if_buffer_edited_since_start(buffer, transaction_id_format, &cx)
-            {
-                zlog::warn!(logger => "Buffer edited while formatting. Aborting");
-                result = Err(err);
+        // handle whitespace formatting
+        if settings.remove_trailing_whitespace_on_save {
+            zlog::trace!(logger => "removing trailing whitespace");
+            let diff = buffer
+                .handle
+                .read_with(cx, |buffer, cx| buffer.remove_trailing_whitespace(cx))?
+                .await;
+            extend_formatting_transaction(buffer, formatting_transaction_id, cx, |buffer, cx| {
+                buffer.apply_diff(diff, cx);
+            })?;
+        }
+
+        if settings.ensure_final_newline_on_save {
+            zlog::trace!(logger => "ensuring final newline");
+            extend_formatting_transaction(buffer, formatting_transaction_id, cx, |buffer, cx| {
+                buffer.ensure_final_newline(cx);
+            })?;
+        }
+
+        // Formatter for `code_actions_on_format` that runs before
+        // the rest of the formatters
+        let mut code_actions_on_format_formatter = None;
+        let should_run_code_actions_on_format = !matches!(
+            (trigger, &settings.format_on_save),
+            (FormatTrigger::Save, &FormatOnSave::Off)
+        );
+        if should_run_code_actions_on_format {
+            let have_code_actions_to_run_on_format = settings
+                .code_actions_on_format
+                .values()
+                .any(|enabled| *enabled);
+            if have_code_actions_to_run_on_format {
+                zlog::trace!(logger => "going to run code actions on format");
+                code_actions_on_format_formatter = Some(Formatter::CodeActions(
+                    settings.code_actions_on_format.clone(),
+                ));
             }
+        }
 
-            // handle whitespace formatting
-            if result.is_ok() {
-                if settings.remove_trailing_whitespace_on_save {
-                    zlog::trace!(logger => "removing trailing whitespace");
-                    let diff = buffer
-                        .handle
-                        .read_with(cx, |buffer, cx| buffer.remove_trailing_whitespace(cx))?
-                        .await;
-                    buffer.handle.update(cx, |buffer, cx| {
-                        buffer.start_transaction();
-                        buffer.apply_diff(diff, cx);
-                        transaction_id_format =
-                            transaction_id_format.or(buffer.end_transaction(cx));
-                        if let Some(transaction_id) = transaction_id_format {
-                            buffer.group_until_transaction(transaction_id);
+        let formatters = match (trigger, &settings.format_on_save) {
+            (FormatTrigger::Save, FormatOnSave::Off) => &[],
+            (FormatTrigger::Save, FormatOnSave::List(formatters)) => formatters.as_ref(),
+            (FormatTrigger::Manual, _) | (FormatTrigger::Save, FormatOnSave::On) => {
+                match &settings.formatter {
+                    SelectedFormatter::Auto => {
+                        if settings.prettier.allowed {
+                            zlog::trace!(logger => "Formatter set to auto: defaulting to prettier");
+                            std::slice::from_ref(&Formatter::Prettier)
+                        } else {
+                            zlog::trace!(logger => "Formatter set to auto: defaulting to primary language server");
+                            std::slice::from_ref(&Formatter::LanguageServer { name: None })
                         }
-                    })?;
+                    }
+                    SelectedFormatter::List(formatter_list) => formatter_list.as_ref(),
                 }
+            }
+        };
 
-                if settings.ensure_final_newline_on_save {
-                    zlog::trace!(logger => "ensuring final newline");
-                    buffer.handle.update(cx, |buffer, cx| {
-                        buffer.start_transaction();
-                        buffer.ensure_final_newline(cx);
-                        transaction_id_format =
-                            transaction_id_format.or(buffer.end_transaction(cx));
-                        if let Some(transaction_id) = transaction_id_format {
-                            buffer.group_until_transaction(transaction_id);
-                        }
+        let formatters = code_actions_on_format_formatter.iter().chain(formatters);
+
+        for formatter in formatters {
+            match formatter {
+                Formatter::Prettier => {
+                    let logger = zlog::scoped!(logger => "prettier");
+                    zlog::trace!(logger => "formatting");
+                    let _timer = zlog::time!(logger => "Formatting buffer via prettier");
+
+                    let prettier = lsp_store.read_with(cx, |lsp_store, _cx| {
+                        lsp_store.prettier_store().unwrap().downgrade()
                     })?;
+                    let diff = prettier_store::format_with_prettier(&prettier, &buffer.handle, cx)
+                        .await
+                        .transpose()?;
+                    let Some(diff) = diff else {
+                        zlog::trace!(logger => "No changes");
+                        continue;
+                    };
+
+                    extend_formatting_transaction(
+                        buffer,
+                        formatting_transaction_id,
+                        cx,
+                        |buffer, cx| {
+                            buffer.apply_diff(diff, cx);
+                        },
+                    )?;
                 }
-            }
+                Formatter::External { command, arguments } => {
+                    let logger = zlog::scoped!(logger => "command");
+                    zlog::trace!(logger => "formatting");
+                    let _timer = zlog::time!(logger => "Formatting buffer via external command");
+
+                    let diff = Self::format_via_external_command(
+                        buffer,
+                        command.as_ref(),
+                        arguments.as_deref(),
+                        cx,
+                    )
+                    .await
+                    .with_context(|| {
+                        format!("Failed to format buffer via external command: {}", command)
+                    })?;
+                    let Some(diff) = diff else {
+                        zlog::trace!(logger => "No changes");
+                        continue;
+                    };
 
-            // Formatter for `code_actions_on_format` that runs before
-            // the rest of the formatters
-            let code_actions_on_format_formatter = 'ca_formatter: {
-                let should_run_code_actions_on_format = !matches!(
-                    (trigger, &settings.format_on_save),
-                    (FormatTrigger::Save, &FormatOnSave::Off)
-                );
-                let have_code_actions_to_run_on_format = settings
-                    .code_actions_on_format
-                    .values()
-                    .any(|enabled| *enabled);
-
-                if should_run_code_actions_on_format {
-                    if have_code_actions_to_run_on_format {
-                        zlog::trace!(logger => "going to run code actions on format");
-                        break 'ca_formatter Some(Formatter::CodeActions(
-                            settings.code_actions_on_format.clone(),
-                        ));
-                    }
+                    extend_formatting_transaction(
+                        buffer,
+                        formatting_transaction_id,
+                        cx,
+                        |buffer, cx| {
+                            buffer.apply_diff(diff, cx);
+                        },
+                    )?;
                 }
-                break 'ca_formatter None;
-            };
+                Formatter::LanguageServer { name } => {
+                    let logger = zlog::scoped!(logger => "language-server");
+                    zlog::trace!(logger => "formatting");
+                    let _timer = zlog::time!(logger => "Formatting buffer using language server");
+
+                    let Some(buffer_path_abs) = buffer.abs_path.as_ref() else {
+                        zlog::warn!(logger => "Cannot format buffer that is not backed by a file on disk using language servers. Skipping");
+                        continue;
+                    };
 
-            let formatters = match (trigger, &settings.format_on_save) {
-                (FormatTrigger::Save, FormatOnSave::Off) => &[],
-                (FormatTrigger::Save, FormatOnSave::List(formatters)) => formatters.as_ref(),
-                (FormatTrigger::Manual, _) | (FormatTrigger::Save, FormatOnSave::On) => {
-                    match &settings.formatter {
-                        SelectedFormatter::Auto => {
-                            if settings.prettier.allowed {
-                                zlog::trace!(logger => "Formatter set to auto: defaulting to prettier");
-                                std::slice::from_ref(&Formatter::Prettier)
+                    let language_server = if let Some(name) = name.as_deref() {
+                        adapters_and_servers.iter().find_map(|(adapter, server)| {
+                            if adapter.name.0.as_ref() == name {
+                                Some(server.clone())
                             } else {
-                                zlog::trace!(logger => "Formatter set to auto: defaulting to primary language server");
-                                std::slice::from_ref(&Formatter::LanguageServer { name: None })
+                                None
                             }
-                        }
-                        SelectedFormatter::List(formatter_list) => formatter_list.as_ref(),
-                    }
-                }
-            };
+                        })
+                    } else {
+                        adapters_and_servers.first().map(|e| e.1.clone())
+                    };
+
+                    let Some(language_server) = language_server else {
+                        log::warn!(
+                            "No language server found to format buffer '{:?}'. Skipping",
+                            buffer_path_abs.as_path().to_string_lossy()
+                        );
+                        continue;
+                    };
 
-            let formatters = code_actions_on_format_formatter.iter().chain(formatters);
+                    zlog::trace!(
+                        logger =>
+                        "Formatting buffer '{:?}' using language server '{:?}'",
+                        buffer_path_abs.as_path().to_string_lossy(),
+                        language_server.name()
+                    );
 
-            'formatters: for formatter in formatters {
-                if result.is_err() {
-                    // may have been set above, instead of indenting this whole block with an if, just don't do anything
-                    // destructive if we're aborting
-                    continue;
-                }
-                match formatter {
-                    Formatter::Prettier => {
-                        let logger = zlog::scoped!(logger => "prettier");
-                        zlog::trace!(logger => "formatting");
-                        let _timer = zlog::time!(logger => "Formatting buffer via prettier");
-
-                        let prettier = lsp_store.read_with(cx, |lsp_store, _cx| {
-                            lsp_store.prettier_store().unwrap().downgrade()
-                        })?;
-                        let diff_result =
-                            prettier_store::format_with_prettier(&prettier, &buffer.handle, cx)
-                                .await
-                                .transpose();
-                        let Ok(diff) = diff_result else {
-                            result = Err(diff_result.unwrap_err());
-                            zlog::error!(logger => "failed, reason: {:?}", result.as_ref());
-                            break 'formatters;
-                        };
-                        let Some(diff) = diff else {
-                            zlog::trace!(logger => "No changes");
-                            continue 'formatters;
-                        };
-                        if let Some(err) =
-                            err_if_buffer_edited_since_start(buffer, transaction_id_format, &cx)
-                        {
-                            zlog::warn!(logger => "Buffer edited while formatting. Aborting");
-                            result = Err(err);
-                            break 'formatters;
-                        }
-                        zlog::trace!(logger => "Applying changes");
-                        buffer.handle.update(cx, |buffer, cx| {
-                            buffer.start_transaction();
-                            buffer.apply_diff(diff, cx);
-                            transaction_id_format =
-                                transaction_id_format.or(buffer.end_transaction(cx));
-                            if let Some(transaction_id) = transaction_id_format {
-                                buffer.group_until_transaction(transaction_id);
-                            }
-                        })?;
-                    }
-                    Formatter::External { command, arguments } => {
-                        let logger = zlog::scoped!(logger => "command");
-                        zlog::trace!(logger => "formatting");
-                        let _timer =
-                            zlog::time!(logger => "Formatting buffer via external command");
-
-                        let diff_result = Self::format_via_external_command(
-                            buffer,
-                            command.as_ref(),
-                            arguments.as_deref(),
+                    let edits = if let Some(ranges) = buffer.ranges.as_ref() {
+                        zlog::trace!(logger => "formatting ranges");
+                        Self::format_ranges_via_lsp(
+                            &lsp_store,
+                            &buffer.handle,
+                            ranges,
+                            buffer_path_abs,
+                            &language_server,
+                            &settings,
                             cx,
                         )
                         .await
-                        .with_context(|| {
-                            format!("Failed to format buffer via external command: {}", command)
-                        });
-                        let Ok(diff) = diff_result else {
-                            result = Err(diff_result.unwrap_err());
-                            zlog::error!(logger => "failed, reason: {:?}", result.as_ref());
-                            break 'formatters;
-                        };
-                        let Some(diff) = diff else {
-                            zlog::trace!(logger => "No changes");
-                            continue 'formatters;
-                        };
-                        if let Some(err) =
-                            err_if_buffer_edited_since_start(buffer, transaction_id_format, &cx)
-                        {
-                            zlog::warn!(logger => "Buffer edited while formatting. Aborting");
-                            result = Err(err);
-                            break 'formatters;
-                        }
-                        zlog::trace!(logger => "Applying changes");
-                        buffer.handle.update(cx, |buffer, cx| {
-                            buffer.start_transaction();
-                            buffer.apply_diff(diff, cx);
-                            transaction_id_format =
-                                transaction_id_format.or(buffer.end_transaction(cx));
-                            if let Some(transaction_id) = transaction_id_format {
-                                buffer.group_until_transaction(transaction_id);
-                            }
-                        })?;
+                        .context("Failed to format ranges via language server")?
+                    } else {
+                        zlog::trace!(logger => "formatting full");
+                        Self::format_via_lsp(
+                            &lsp_store,
+                            &buffer.handle,
+                            buffer_path_abs,
+                            &language_server,
+                            &settings,
+                            cx,
+                        )
+                        .await
+                        .context("failed to format via language server")?
+                    };
+
+                    if edits.is_empty() {
+                        zlog::trace!(logger => "No changes");
+                        continue;
                     }
-                    Formatter::LanguageServer { name } => {
-                        let logger = zlog::scoped!(logger => "language-server");
-                        zlog::trace!(logger => "formatting");
-                        let _timer =
-                            zlog::time!(logger => "Formatting buffer using language server");
-
-                        let Some(buffer_path_abs) = buffer.abs_path.as_ref() else {
-                            zlog::warn!(logger => "Cannot format buffer that is not backed by a file on disk using language servers. Skipping");
-                            continue 'formatters;
-                        };
+                    extend_formatting_transaction(
+                        buffer,
+                        formatting_transaction_id,
+                        cx,
+                        |buffer, cx| {
+                            buffer.edit(edits, None, cx);
+                        },
+                    )?;
+                }
+                Formatter::CodeActions(code_actions) => {
+                    let logger = zlog::scoped!(logger => "code-actions");
+                    zlog::trace!(logger => "formatting");
+                    let _timer = zlog::time!(logger => "Formatting buffer using code actions");
 
-                        let language_server = 'language_server: {
-                            // if a name was provided, try to find the server with that name
-                            if let Some(name) = name.as_deref() {
-                                for (adapter, server) in &adapters_and_servers {
-                                    if adapter.name.0.as_ref() == name {
-                                        break 'language_server server.clone();
-                                    }
-                                }
-                            }
+                    let Some(buffer_path_abs) = buffer.abs_path.as_ref() else {
+                        zlog::warn!(logger => "Cannot format buffer that is not backed by a file on disk using code actions. Skipping");
+                        continue;
+                    };
+                    let code_action_kinds = code_actions
+                        .iter()
+                        .filter_map(|(action_kind, enabled)| {
+                            enabled.then_some(action_kind.clone().into())
+                        })
+                        .collect::<Vec<_>>();
+                    if code_action_kinds.is_empty() {
+                        zlog::trace!(logger => "No code action kinds enabled, skipping");
+                        continue;
+                    }
+                    zlog::trace!(logger => "Attempting to resolve code actions {:?}", &code_action_kinds);
 
-                            // otherwise, fall back to the primary language server for the buffer if one exists
-                            let default_lsp = lsp_store.update(cx, |lsp_store, cx| {
-                                buffer.handle.update(cx, |buffer, cx| {
-                                    lsp_store
-                                        .as_local()
-                                        .unwrap()
-                                        .primary_language_server_for_buffer(buffer, cx)
-                                        .map(|(_, lsp)| lsp.clone())
-                                })
-                            })?;
+                    let mut actions_and_servers = Vec::new();
 
-                            if let Some(default_lsp) = default_lsp {
-                                break 'language_server default_lsp;
-                            } else {
-                                log::warn!(
-                                    "No language server found to format buffer '{:?}'. Skipping",
-                                    buffer_path_abs.as_path().to_string_lossy()
-                                );
-                                continue 'formatters;
-                            }
+                    for (index, (_, language_server)) in adapters_and_servers.iter().enumerate() {
+                        let actions_result = Self::get_server_code_actions_from_action_kinds(
+                            &lsp_store,
+                            language_server.server_id(),
+                            code_action_kinds.clone(),
+                            &buffer.handle,
+                            cx,
+                        )
+                        .await
+                        .with_context(
+                            || format!("Failed to resolve code actions with kinds {:?} for language server {}",
+                                code_action_kinds.iter().map(|kind| kind.as_str()).join(", "),
+                                language_server.name())
+                        );
+                        let Ok(actions) = actions_result else {
+                            // note: it may be better to set result to the error and break formatters here
+                            // but for now we try to execute the actions that we can resolve and skip the rest
+                            zlog::error!(
+                                logger =>
+                                "Failed to resolve code actions with kinds {:?} with language server {}",
+                                code_action_kinds.iter().map(|kind| kind.as_str()).join(", "),
+                                language_server.name()
+                            );
+                            continue;
                         };
+                        for action in actions {
+                            actions_and_servers.push((action, index));
+                        }
+                    }
 
-                        zlog::trace!(
-                            logger =>
-                            "Formatting buffer '{:?}' using language server '{:?}'",
-                            buffer_path_abs.as_path().to_string_lossy(),
-                            language_server.name()
-                        );
+                    if actions_and_servers.is_empty() {
+                        zlog::warn!(logger => "No code actions were resolved, continuing");
+                        continue;
+                    }
 
-                        let edits_result = if let Some(ranges) = buffer.ranges.as_ref() {
-                            zlog::trace!(logger => "formatting ranges");
-                            Self::format_ranges_via_lsp(
-                                &lsp_store,
-                                &buffer.handle,
-                                ranges,
-                                buffer_path_abs,
-                                &language_server,
-                                &settings,
-                                cx,
+                    'actions: for (mut action, server_index) in actions_and_servers {
+                        let server = &adapters_and_servers[server_index].1;
+
+                        let describe_code_action = |action: &CodeAction| {
+                            format!(
+                                "code action '{}' with title \"{}\" on server {}",
+                                action
+                                    .lsp_action
+                                    .action_kind()
+                                    .unwrap_or("unknown".into())
+                                    .as_str(),
+                                action.lsp_action.title(),
+                                server.name(),
                             )
-                            .await
-                            .context("Failed to format ranges via language server")
-                        } else {
-                            zlog::trace!(logger => "formatting full");
-                            Self::format_via_lsp(
-                                &lsp_store,
-                                &buffer.handle,
-                                buffer_path_abs,
-                                &language_server,
-                                &settings,
-                                cx,
-                            )
-                            .await
-                            .context("failed to format via language server")
                         };
 
-                        let Ok(edits) = edits_result else {
-                            result = Err(edits_result.unwrap_err());
-                            zlog::error!(logger => "Failed, reason: {:?}", result.as_ref());
-                            break 'formatters;
-                        };
+                        zlog::trace!(logger => "Executing {}", describe_code_action(&action));
 
-                        if edits.is_empty() {
-                            zlog::trace!(logger => "No changes");
-                            continue 'formatters;
-                        }
-                        if let Some(err) =
-                            err_if_buffer_edited_since_start(buffer, transaction_id_format, &cx)
-                        {
-                            zlog::warn!(logger => "Buffer edited while formatting. Aborting");
-                            result = Err(err);
-                            break 'formatters;
-                        }
-                        zlog::trace!(logger => "Applying changes");
-                        buffer.handle.update(cx, |buffer, cx| {
-                            buffer.start_transaction();
-                            buffer.edit(edits, None, cx);
-                            transaction_id_format =
-                                transaction_id_format.or(buffer.end_transaction(cx));
-                            if let Some(transaction_id) = transaction_id_format {
-                                buffer.group_until_transaction(transaction_id);
-                            }
-                        })?;
-                    }
-                    Formatter::CodeActions(code_actions) => {
-                        let logger = zlog::scoped!(logger => "code-actions");
-                        zlog::trace!(logger => "formatting");
-                        let _timer = zlog::time!(logger => "Formatting buffer using code actions");
-
-                        let Some(buffer_path_abs) = buffer.abs_path.as_ref() else {
-                            zlog::warn!(logger => "Cannot format buffer that is not backed by a file on disk using code actions. Skipping");
-                            continue 'formatters;
-                        };
-                        let code_action_kinds = code_actions
-                            .iter()
-                            .filter_map(|(action_kind, enabled)| {
-                                enabled.then_some(action_kind.clone().into())
-                            })
-                            .collect::<Vec<_>>();
-                        if code_action_kinds.is_empty() {
-                            zlog::trace!(logger => "No code action kinds enabled, skipping");
-                            continue 'formatters;
+                        if let Err(err) = Self::try_resolve_code_action(server, &mut action).await {
+                            zlog::error!(
+                                logger =>
+                                "Failed to resolve {}. Error: {}",
+                                describe_code_action(&action),
+                                err
+                            );
+                            continue;
                         }
 
-                        let mut actions_and_servers = Vec::new();
-
-                        for (index, (_, language_server)) in adapters_and_servers.iter().enumerate()
-                        {
-                            let actions_result = Self::get_server_code_actions_from_action_kinds(
-                                        &lsp_store,
-                                        language_server.server_id(),
-                                        code_action_kinds.clone(),
-                                        &buffer.handle,
-                                        cx,
-                                    )
-                                    .await
-                                    .with_context(
-                                        || format!("Failed to resolve code actions with kinds {:?} for language server {}",
-                                            code_action_kinds.iter().map(|kind| kind.as_str()).join(", "),
-                                            language_server.name())
-                                    );
-                            let Ok(actions) = actions_result else {
-                                // note: it may be better to set result to the error and break formatters here
-                                // but for now we try to execute the actions that we can resolve and skip the rest
-                                zlog::error!(
+                        if let Some(edit) = action.lsp_action.edit().cloned() {
+                            // NOTE: code below duplicated from `Self::deserialize_workspace_edit`
+                            // but filters out and logs warnings for code actions that cause unreasonably
+                            // difficult handling on our part, such as:
+                            // - applying edits that call commands
+                            //   which can result in arbitrary workspace edits being sent from the server that
+                            //   have no way of being tied back to the command that initiated them (i.e. we
+                            //   can't know which edits are part of the format request, or if the server is done sending
+                            //   actions in response to the command)
+                            // - actions that create/delete/modify/rename files other than the one we are formatting
+                            //   as we then would need to handle such changes correctly in the local history as well
+                            //   as the remote history through the ProjectTransaction
+                            // - actions with snippet edits, as these simply don't make sense in the context of a format request
+                            // Supporting these actions is not impossible, but not supported as of yet.
+                            if edit.changes.is_none() && edit.document_changes.is_none() {
+                                zlog::trace!(
                                     logger =>
-                                    "Failed to resolve code actions with kinds {:?} with language server {}",
-                                    code_action_kinds.iter().map(|kind| kind.as_str()).join(", "),
-                                    language_server.name()
+                                    "No changes for code action. Skipping {}",
+                                    describe_code_action(&action),
                                 );
                                 continue;
-                            };
-                            for action in actions {
-                                actions_and_servers.push((action, index));
                             }
-                        }
-
-                        if actions_and_servers.is_empty() {
-                            zlog::trace!(logger => "No code actions were resolved, continuing");
-                            continue 'formatters;
-                        }
 
-                        'actions: for (mut action, server_index) in actions_and_servers {
-                            let server = &adapters_and_servers[server_index].1;
-
-                            let describe_code_action = |action: &CodeAction| {
-                                format!(
-                                    "code action '{}' with title \"{}\" on server {}",
-                                    action
-                                        .lsp_action
-                                        .action_kind()
-                                        .unwrap_or("unknown".into())
-                                        .as_str(),
-                                    action.lsp_action.title(),
-                                    server.name(),
-                                )
-                            };
+                            let mut operations = Vec::new();
+                            if let Some(document_changes) = edit.document_changes {
+                                match document_changes {
+                                    lsp::DocumentChanges::Edits(edits) => operations.extend(
+                                        edits.into_iter().map(lsp::DocumentChangeOperation::Edit),
+                                    ),
+                                    lsp::DocumentChanges::Operations(ops) => operations = ops,
+                                }
+                            } else if let Some(changes) = edit.changes {
+                                operations.extend(changes.into_iter().map(|(uri, edits)| {
+                                    lsp::DocumentChangeOperation::Edit(lsp::TextDocumentEdit {
+                                        text_document:
+                                            lsp::OptionalVersionedTextDocumentIdentifier {
+                                                uri,
+                                                version: None,
+                                            },
+                                        edits: edits.into_iter().map(Edit::Plain).collect(),
+                                    })
+                                }));
+                            }
 
-                            zlog::trace!(logger => "Executing {}", describe_code_action(&action));
+                            let mut edits = Vec::with_capacity(operations.len());
 
-                            if let Err(err) =
-                                Self::try_resolve_code_action(server, &mut action).await
-                            {
-                                zlog::error!(
+                            if operations.is_empty() {
+                                zlog::trace!(
                                     logger =>
-                                    "Failed to resolve {}. Error: {}",
+                                    "No changes for code action. Skipping {}",
                                     describe_code_action(&action),
-                                    err
                                 );
-                                continue 'actions;
+                                continue;
                             }
-
-                            if let Some(edit) = action.lsp_action.edit().cloned() {
-                                // NOTE: code below duplicated from `Self::deserialize_workspace_edit`
-                                // but filters out and logs warnings for code actions that cause unreasonably
-                                // difficult handling on our part, such as:
-                                // - applying edits that call commands
-                                //   which can result in arbitrary workspace edits being sent from the server that
-                                //   have no way of being tied back to the command that initiated them (i.e. we
-                                //   can't know which edits are part of the format request, or if the server is done sending
-                                //   actions in response to the command)
-                                // - actions that create/delete/modify/rename files other than the one we are formatting
-                                //   as we then would need to handle such changes correctly in the local history as well
-                                //   as the remote history through the ProjectTransaction
-                                // - actions with snippet edits, as these simply don't make sense in the context of a format request
-                                // Supporting these actions is not impossible, but not supported as of yet.
-                                if edit.changes.is_none() && edit.document_changes.is_none() {
-                                    zlog::trace!(
+                            for operation in operations {
+                                let op = match operation {
+                                    lsp::DocumentChangeOperation::Edit(op) => op,
+                                    lsp::DocumentChangeOperation::Op(_) => {
+                                        zlog::warn!(
+                                            logger =>
+                                            "Code actions which create, delete, or rename files are not supported on format. Skipping {}",
+                                            describe_code_action(&action),
+                                        );
+                                        continue 'actions;
+                                    }
+                                };
+                                let Ok(file_path) = op.text_document.uri.to_file_path() else {
+                                    zlog::warn!(
                                         logger =>
-                                        "No changes for code action. Skipping {}",
+                                        "Failed to convert URI '{:?}' to file path. Skipping {}",
+                                        &op.text_document.uri,
                                         describe_code_action(&action),
                                     );
                                     continue 'actions;
-                                }
-
-                                let mut operations = Vec::new();
-                                if let Some(document_changes) = edit.document_changes {
-                                    match document_changes {
-                                        lsp::DocumentChanges::Edits(edits) => operations.extend(
-                                            edits
-                                                .into_iter()
-                                                .map(lsp::DocumentChangeOperation::Edit),
-                                        ),
-                                        lsp::DocumentChanges::Operations(ops) => operations = ops,
-                                    }
-                                } else if let Some(changes) = edit.changes {
-                                    operations.extend(changes.into_iter().map(|(uri, edits)| {
-                                        lsp::DocumentChangeOperation::Edit(lsp::TextDocumentEdit {
-                                            text_document:
-                                                lsp::OptionalVersionedTextDocumentIdentifier {
-                                                    uri,
-                                                    version: None,
-                                                },
-                                            edits: edits.into_iter().map(Edit::Plain).collect(),
-                                        })
-                                    }));
-                                }
-
-                                let mut edits = Vec::with_capacity(operations.len());
-
-                                if operations.is_empty() {
-                                    zlog::trace!(
+                                };
+                                if &file_path != buffer_path_abs {
+                                    zlog::warn!(
                                         logger =>
-                                        "No changes for code action. Skipping {}",
+                                        "File path '{:?}' does not match buffer path '{:?}'. Skipping {}",
+                                        file_path,
+                                        buffer_path_abs,
                                         describe_code_action(&action),
                                     );
                                     continue 'actions;
                                 }
-                                for operation in operations {
-                                    let op = match operation {
-                                        lsp::DocumentChangeOperation::Edit(op) => op,
-                                        lsp::DocumentChangeOperation::Op(_) => {
+
+                                let mut lsp_edits = Vec::new();
+                                for edit in op.edits {
+                                    match edit {
+                                        Edit::Plain(edit) => {
+                                            if !lsp_edits.contains(&edit) {
+                                                lsp_edits.push(edit);
+                                            }
+                                        }
+                                        Edit::Annotated(edit) => {
+                                            if !lsp_edits.contains(&edit.text_edit) {
+                                                lsp_edits.push(edit.text_edit);
+                                            }
+                                        }
+                                        Edit::Snippet(_) => {
                                             zlog::warn!(
                                                 logger =>
-                                                "Code actions which create, delete, or rename files are not supported on format. Skipping {}",
+                                                "Code actions which produce snippet edits are not supported during formatting. Skipping {}",
                                                 describe_code_action(&action),
                                             );
                                             continue 'actions;
                                         }
-                                    };
-                                    let Ok(file_path) = op.text_document.uri.to_file_path() else {
-                                        zlog::warn!(
-                                            logger =>
-                                            "Failed to convert URI '{:?}' to file path. Skipping {}",
-                                            &op.text_document.uri,
-                                            describe_code_action(&action),
-                                        );
-                                        continue 'actions;
-                                    };
-                                    if &file_path != buffer_path_abs {
-                                        zlog::warn!(
-                                            logger =>
-                                            "File path '{:?}' does not match buffer path '{:?}'. Skipping {}",
-                                            file_path,
-                                            buffer_path_abs,
-                                            describe_code_action(&action),
-                                        );
-                                        continue 'actions;
-                                    }
-
-                                    let mut lsp_edits = Vec::new();
-                                    for edit in op.edits {
-                                        match edit {
-                                            Edit::Plain(edit) => {
-                                                if !lsp_edits.contains(&edit) {
-                                                    lsp_edits.push(edit);
-                                                }
-                                            }
-                                            Edit::Annotated(edit) => {
-                                                if !lsp_edits.contains(&edit.text_edit) {
-                                                    lsp_edits.push(edit.text_edit);
-                                                }
-                                            }
-                                            Edit::Snippet(_) => {
-                                                zlog::warn!(
-                                                    logger =>
-                                                    "Code actions which produce snippet edits are not supported during formatting. Skipping {}",
-                                                    describe_code_action(&action),
-                                                );
-                                                continue 'actions;
-                                            }
-                                        }
                                     }
-                                    let edits_result = lsp_store
-                                        .update(cx, |lsp_store, cx| {
-                                            lsp_store.as_local_mut().unwrap().edits_from_lsp(
-                                                &buffer.handle,
-                                                lsp_edits,
-                                                server.server_id(),
-                                                op.text_document.version,
-                                                cx,
-                                            )
-                                        })?
-                                        .await;
-                                    let Ok(resolved_edits) = edits_result else {
-                                        zlog::warn!(
-                                            logger =>
-                                            "Failed to resolve edits from LSP for buffer {:?} while handling {}",
-                                            buffer_path_abs.as_path(),
-                                            describe_code_action(&action),
-                                        );
-                                        continue 'actions;
-                                    };
-                                    edits.extend(resolved_edits);
                                 }
-
-                                if edits.is_empty() {
-                                    zlog::warn!(logger => "No edits resolved from LSP");
+                                let edits_result = lsp_store
+                                    .update(cx, |lsp_store, cx| {
+                                        lsp_store.as_local_mut().unwrap().edits_from_lsp(
+                                            &buffer.handle,
+                                            lsp_edits,
+                                            server.server_id(),
+                                            op.text_document.version,
+                                            cx,
+                                        )
+                                    })?
+                                    .await;
+                                let Ok(resolved_edits) = edits_result else {
+                                    zlog::warn!(
+                                        logger =>
+                                        "Failed to resolve edits from LSP for buffer {:?} while handling {}",
+                                        buffer_path_abs.as_path(),
+                                        describe_code_action(&action),
+                                    );
                                     continue 'actions;
-                                }
+                                };
+                                edits.extend(resolved_edits);
+                            }
 
-                                if let Some(err) = err_if_buffer_edited_since_start(
-                                    buffer,
-                                    transaction_id_format,
-                                    &cx,
-                                ) {
-                                    zlog::warn!(logger => "Buffer edited while formatting. Aborting");
-                                    result = Err(err);
-                                    break 'formatters;
-                                }
-                                zlog::info!(logger => "Applying changes");
-                                buffer.handle.update(cx, |buffer, cx| {
-                                    buffer.start_transaction();
-                                    buffer.edit(edits, None, cx);
-                                    transaction_id_format =
-                                        transaction_id_format.or(buffer.end_transaction(cx));
-                                    if let Some(transaction_id) = transaction_id_format {
-                                        buffer.group_until_transaction(transaction_id);
-                                    }
-                                })?;
+                            if edits.is_empty() {
+                                zlog::warn!(logger => "No edits resolved from LSP");
+                                continue;
                             }
-                            if let Some(command) = action.lsp_action.command() {
+
+                            extend_formatting_transaction(
+                                buffer,
+                                formatting_transaction_id,
+                                cx,
+                                |buffer, cx| {
+                                    buffer.edit(edits, None, cx);
+                                },
+                            )?;
+                        }
+
+                        if let Some(command) = action.lsp_action.command() {
+                            zlog::warn!(
+                                logger =>
+                                "Executing code action command '{}'. This may cause formatting to abort unnecessarily as well as splitting formatting into two entries in the undo history",
+                                &command.command,
+                            );
+
+                            // bail early if command is invalid
+                            let server_capabilities = server.capabilities();
+                            let available_commands = server_capabilities
+                                .execute_command_provider
+                                .as_ref()
+                                .map(|options| options.commands.as_slice())
+                                .unwrap_or_default();
+                            if !available_commands.contains(&command.command) {
                                 zlog::warn!(
                                     logger =>
-                                    "Executing code action command '{}'. This may cause formatting to abort unnecessarily as well as splitting formatting into two entries in the undo history",
-                                    &command.command,
+                                    "Cannot execute a command {} not listed in the language server capabilities of server {}",
+                                    command.command,
+                                    server.name(),
                                 );
-                                // bail early and command is invalid
-                                {
-                                    let server_capabilities = server.capabilities();
-                                    let available_commands = server_capabilities
-                                        .execute_command_provider
-                                        .as_ref()
-                                        .map(|options| options.commands.as_slice())
-                                        .unwrap_or_default();
-                                    if !available_commands.contains(&command.command) {
-                                        zlog::warn!(
-                                            logger =>
-                                            "Cannot execute a command {} not listed in the language server capabilities of server {}",
-                                            command.command,
-                                            server.name(),
-                                        );
-                                        continue 'actions;
-                                    }
-                                }
+                                continue;
+                            }
 
-                                if let Some(err) = err_if_buffer_edited_since_start(
-                                    buffer,
-                                    transaction_id_format,
-                                    &cx,
-                                ) {
-                                    zlog::warn!(logger => "Buffer edited while formatting. Aborting");
-                                    result = Err(err);
-                                    break 'formatters;
-                                }
-                                zlog::info!(logger => "Executing command {}", &command.command);
+                            // noop so we just ensure buffer hasn't been edited since resolving code actions
+                            extend_formatting_transaction(
+                                buffer,
+                                formatting_transaction_id,
+                                cx,
+                                |_, _| {},
+                            )?;
+                            zlog::info!(logger => "Executing command {}", &command.command);
+
+                            lsp_store.update(cx, |this, _| {
+                                this.as_local_mut()
+                                    .unwrap()
+                                    .last_workspace_edits_by_language_server
+                                    .remove(&server.server_id());
+                            })?;
+
+                            let execute_command_result = server
+                                .request::<lsp::request::ExecuteCommand>(
+                                    lsp::ExecuteCommandParams {
+                                        command: command.command.clone(),
+                                        arguments: command.arguments.clone().unwrap_or_default(),
+                                        ..Default::default()
+                                    },
+                                )
+                                .await;
+
+                            if execute_command_result.is_err() {
+                                zlog::error!(
+                                    logger =>
+                                    "Failed to execute command '{}' as part of {}",
+                                    &command.command,
+                                    describe_code_action(&action),
+                                );
+                                continue 'actions;
+                            }
 
+                            let mut project_transaction_command =
                                 lsp_store.update(cx, |this, _| {
                                     this.as_local_mut()
                                         .unwrap()
                                         .last_workspace_edits_by_language_server
-                                        .remove(&server.server_id());
+                                        .remove(&server.server_id())
+                                        .unwrap_or_default()
                                 })?;
 
-                                let execute_command_result = server
-                                    .request::<lsp::request::ExecuteCommand>(
-                                        lsp::ExecuteCommandParams {
-                                            command: command.command.clone(),
-                                            arguments: command
-                                                .arguments
-                                                .clone()
-                                                .unwrap_or_default(),
-                                            ..Default::default()
-                                        },
-                                    )
-                                    .await;
-
-                                if execute_command_result.is_err() {
-                                    zlog::error!(
-                                        logger =>
-                                        "Failed to execute command '{}' as part of {}",
-                                        &command.command,
-                                        describe_code_action(&action),
-                                    );
-                                    continue 'actions;
-                                }
-
-                                let mut project_transaction_command =
-                                    lsp_store.update(cx, |this, _| {
-                                        this.as_local_mut()
-                                            .unwrap()
-                                            .last_workspace_edits_by_language_server
-                                            .remove(&server.server_id())
-                                            .unwrap_or_default()
-                                    })?;
-
-                                if let Some(transaction) =
-                                    project_transaction_command.0.remove(&buffer.handle)
-                                {
-                                    zlog::trace!(
-                                        logger =>
-                                        "Successfully captured {} edits that resulted from command {}",
-                                        transaction.edit_ids.len(),
-                                        &command.command,
-                                    );
-                                    if let Some(transaction_id_format) = transaction_id_format {
-                                        let transaction_id_project_transaction = transaction.id;
-                                        buffer.handle.update(cx, |buffer, _| {
-                                            // it may have been removed from history if push_to_history was
-                                            // false in deserialize_workspace_edit. If so push it so we
-                                            // can merge it with the format transaction
-                                            // and pop the combined transaction off the history stack
-                                            // later if push_to_history is false
-                                            if buffer.get_transaction(transaction.id).is_none() {
-                                                buffer
-                                                    .push_transaction(transaction, Instant::now());
-                                            }
-                                            buffer.merge_transactions(
-                                                transaction_id_project_transaction,
-                                                transaction_id_format,
-                                            );
-                                        })?;
-                                    } else {
-                                        transaction_id_format = Some(transaction.id);
+                            if let Some(transaction) =
+                                project_transaction_command.0.remove(&buffer.handle)
+                            {
+                                zlog::trace!(
+                                    logger =>
+                                    "Successfully captured {} edits that resulted from command {}",
+                                    transaction.edit_ids.len(),
+                                    &command.command,
+                                );
+                                let transaction_id_project_transaction = transaction.id;
+                                buffer.handle.update(cx, |buffer, _| {
+                                    // it may have been removed from history if push_to_history was
+                                    // false in deserialize_workspace_edit. If so push it so we
+                                    // can merge it with the format transaction
+                                    // and pop the combined transaction off the history stack
+                                    // later if push_to_history is false
+                                    if buffer.get_transaction(transaction.id).is_none() {
+                                        buffer.push_transaction(transaction, Instant::now());
                                     }
-                                }
-                                if !project_transaction_command.0.is_empty() {
-                                    let extra_buffers = project_transaction_command
-                                        .0
-                                        .keys()
-                                        .filter_map(|buffer_handle| {
-                                            buffer_handle
-                                                .read_with(cx, |b, cx| b.project_path(cx))
-                                                .ok()
-                                                .flatten()
-                                        })
-                                        .map(|p| p.path.to_sanitized_string())
-                                        .join(", ");
-                                    zlog::warn!(
-                                        logger =>
-                                        "Unexpected edits to buffers other than the buffer actively being formatted due to command {}. Impacted buffers: [{}].",
-                                        &command.command,
-                                        extra_buffers,
+                                    buffer.merge_transactions(
+                                        transaction_id_project_transaction,
+                                        formatting_transaction_id,
                                     );
-                                    for (buffer_handle, transaction) in
-                                        project_transaction_command.0
-                                    {
-                                        let entry = project_transaction.0.entry(buffer_handle);
-                                        use std::collections::hash_map::Entry;
-                                        match entry {
-                                            // if project_transaction already contains a transaction for this buffer, then
-                                            // we already formatted it, and we need to merge the new transaction into the one
-                                            // in project_transaction that we created while formatting
-                                            Entry::Occupied(mut occupied_entry) => {
-                                                let buffer_handle = occupied_entry.key();
-                                                buffer_handle.update(cx, |buffer, _| {
-                                                    // if push_to_history is true, then we need to make sure it is merged in the
-                                                    // buffer history as well
-                                                    if push_to_history {
-                                                        let transaction_id_project_transaction =
-                                                            transaction.id;
-                                                        // ensure transaction from command project transaction
-                                                        // is in history so we can merge
-                                                        if buffer
-                                                            .get_transaction(transaction.id)
-                                                            .is_none()
-                                                        {
-                                                            buffer.push_transaction(
-                                                                transaction.clone(),
-                                                                Instant::now(),
-                                                            );
-                                                        }
-                                                        let transaction_id_existing =
-                                                            occupied_entry.get().id;
-                                                        buffer.merge_transactions(
-                                                            transaction_id_project_transaction,
-                                                            transaction_id_existing,
-                                                        );
-                                                    }
-                                                })?;
-                                                let transaction_existing = occupied_entry.get_mut();
-                                                transaction_existing.merge_in(transaction);
-                                            }
-                                            // if there is no transaction in project_transaction, we either haven't formatted the buffer yet,
-                                            // or we won't format this buffer
-                                            // later iterations over the formattable_buffers list will use this as the starting transaction
-                                            // for the formatting on that buffer
-                                            Entry::Vacant(vacant_entry) => {
-                                                vacant_entry.insert(transaction);
-                                            }
-                                        }
-                                    }
-                                }
+                                })?;
+                            }
+
+                            if !project_transaction_command.0.is_empty() {
+                                let extra_buffers = project_transaction_command
+                                    .0
+                                    .keys()
+                                    .filter_map(|buffer_handle| {
+                                        buffer_handle
+                                            .read_with(cx, |b, cx| b.project_path(cx))
+                                            .ok()
+                                            .flatten()
+                                    })
+                                    .map(|p| p.path.to_sanitized_string())
+                                    .join(", ");
+                                zlog::warn!(
+                                    logger =>
+                                    "Unexpected edits to buffers other than the buffer actively being formatted due to command {}. Impacted buffers: [{}].",
+                                    &command.command,
+                                    extra_buffers,
+                                );
+                                // NOTE: if this case is hit, the proper thing to do is to for each buffer, merge the extra transaction
+                                // into the existing transaction in project_transaction if there is one, and if there isn't one in project_transaction,
+                                // add it so it's included, and merge it into the format transaction when its created later
                             }
                         }
                     }
                 }
             }
-
-            let buffer_handle = buffer.handle.clone();
-            buffer.handle.update(cx, |buffer, _| {
-                let Some(transaction_id) = transaction_id_format else {
-                    zlog::trace!(logger => "No formatting transaction id");
-                    return result;
-                };
-                let Some(transaction_id_last) =
-                    buffer.peek_undo_stack().map(|t| t.transaction_id())
-                else {
-                    // unwrapping should work here, how would we get a transaction id
-                    // with no transaction on the undo stack?
-                    // *but* it occasionally panics. Avoiding panics for now...
-                    zlog::warn!(logger => "No transaction present on undo stack, despite having a formatting transaction id?");
-                    return result;
-                };
-                if transaction_id_last != transaction_id {
-                    zlog::trace!(logger => "Last transaction on undo stack is not the formatting transaction, skipping finalization & update of project transaction");
-                    return result;
-                }
-                let transaction = buffer
-                    .finalize_last_transaction()
-                    .cloned()
-                    .expect("There is a transaction on the undo stack if we were able to peek it");
-                // debug_assert_eq!(transaction.id, transaction_id);
-                if !push_to_history {
-                    zlog::trace!(logger => "forgetting format transaction");
-                    buffer.forget_transaction(transaction.id);
-                }
-                project_transaction
-                    .0
-                    .insert(buffer_handle.clone(), transaction);
-                return result;
-            })??;
         }
 
-        return Ok(project_transaction);
+        Ok(())
     }
 
     pub async fn format_ranges_via_lsp(