Use Transaction instead of ProjectTransaction

Kirill Bulatov and Antonio Scandurra created

Co-Authored-By: Antonio Scandurra <antonio@zed.dev>

Change summary

crates/collab/src/tests/integration_tests.rs | 30 ++++++++-------
crates/editor/src/editor.rs                  | 15 +------
crates/project/src/lsp_command.rs            | 36 +++++++----------
crates/project/src/project.rs                | 44 ++++++++--------------
crates/rpc/proto/zed.proto                   |  2 
crates/rpc/src/rpc.rs                        |  2 
6 files changed, 51 insertions(+), 78 deletions(-)

Detailed changes

crates/collab/src/tests/integration_tests.rs 🔗

@@ -7444,18 +7444,11 @@ async fn test_on_input_format_from_host_to_guest(
 
     let fake_language_server = fake_language_servers.next().await.unwrap();
     cx_b.foreground().run_until_parked();
-    // Type a on type formatting trigger character as the guest.
-    editor_a.update(cx_a, |editor, cx| {
-        editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
-        editor.handle_input(">", cx);
-        cx.focus(&editor_a);
-    });
 
     // Receive an OnTypeFormatting request as the host's language server.
     // Return some formattings from the host's language server.
-    cx_b.foreground().start_waiting();
-    fake_language_server
-        .handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
+    fake_language_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(
+        |params, _| async move {
             assert_eq!(
                 params.text_document_position.text_document.uri,
                 lsp::Url::from_file_path("/a/main.rs").unwrap(),
@@ -7469,18 +7462,27 @@ async fn test_on_input_format_from_host_to_guest(
                 new_text: "~<".to_string(),
                 range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
             }]))
-        })
-        .next()
-        .await
-        .unwrap();
-    cx_b.foreground().finish_waiting();
+        },
+    );
+    // .next()
+    // .await
+    // .unwrap();
 
     // Open the buffer on the guest and see that the formattings worked
     let buffer_b = project_b
         .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
         .await
         .unwrap();
+
+    // Type a on type formatting trigger character as the guest.
+    editor_a.update(cx_a, |editor, cx| {
+        cx.focus(&editor_a);
+        editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
+        editor.handle_input(">", cx);
+    });
+
     cx_b.foreground().run_until_parked();
+
     buffer_b.read_with(cx_b, |buffer, _| {
         assert_eq!(buffer.text(), "fn main() { a>~< }")
     });

crates/editor/src/editor.rs 🔗

@@ -2518,8 +2518,6 @@ impl Editor {
             return None;
         }
 
-        let transaction_title = format!("OnTypeFormatting after {input}");
-        let workspace = self.workspace(cx)?;
         let project = self.project.as_ref()?;
         let position = self.selections.newest_anchor().head();
         let (buffer, buffer_position) = self
@@ -2527,20 +2525,11 @@ impl Editor {
             .read(cx)
             .text_anchor_for_position(position.clone(), cx)?;
         let on_type_formatting = project.update(cx, |project, cx| {
-            project.on_type_format(buffer, buffer_position, input, cx)
+            project.on_type_format(buffer, buffer_position, input, true, cx)
         });
 
         Some(cx.spawn(|editor, mut cx| async move {
-            let project_transaction = on_type_formatting.await?;
-            Self::open_project_transaction(
-                &editor,
-                workspace.downgrade(),
-                project_transaction,
-                transaction_title,
-                cx.clone(),
-            )
-            .await?;
-
+            on_type_formatting.await?;
             editor.update(&mut cx, |editor, cx| {
                 editor.refresh_document_highlights(cx);
             })?;

crates/project/src/lsp_command.rs 🔗

@@ -12,7 +12,7 @@ use language::{
     point_from_lsp, point_to_lsp,
     proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
     range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CachedLspAdapter, CharKind, CodeAction,
-    Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Unclipped,
+    Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction, Unclipped,
 };
 use lsp::{DocumentHighlightKind, LanguageServer, LanguageServerId, ServerCapabilities};
 use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc};
@@ -1628,7 +1628,7 @@ impl LspCommand for GetCodeActions {
 
 #[async_trait(?Send)]
 impl LspCommand for OnTypeFormatting {
-    type Response = ProjectTransaction;
+    type Response = Option<Transaction>;
     type LspRequest = lsp::request::OnTypeFormatting;
     type ProtoRequest = proto::OnTypeFormatting;
 
@@ -1668,7 +1668,7 @@ impl LspCommand for OnTypeFormatting {
         buffer: ModelHandle<Buffer>,
         server_id: LanguageServerId,
         mut cx: AsyncAppContext,
-    ) -> Result<ProjectTransaction> {
+    ) -> Result<Option<Transaction>> {
         if let Some(edits) = message {
             let (lsp_adapter, lsp_server) =
                 language_server_for_buffer(&project, &buffer, server_id, &mut cx)?;
@@ -1683,7 +1683,7 @@ impl LspCommand for OnTypeFormatting {
             )
             .await
         } else {
-            Ok(ProjectTransaction::default())
+            Ok(None)
         }
     }
 
@@ -1729,33 +1729,27 @@ impl LspCommand for OnTypeFormatting {
     }
 
     fn response_to_proto(
-        response: ProjectTransaction,
-        project: &mut Project,
-        peer_id: PeerId,
+        response: Option<Transaction>,
+        _: &mut Project,
+        _: PeerId,
         _: &clock::Global,
-        cx: &mut AppContext,
+        _: &mut AppContext,
     ) -> proto::OnTypeFormattingResponse {
-        let transaction = project.serialize_project_transaction_for_peer(response, peer_id, cx);
         proto::OnTypeFormattingResponse {
-            transaction: Some(transaction),
+            transaction: response
+                .map(|transaction| language::proto::serialize_transaction(&transaction)),
         }
     }
 
     async fn response_from_proto(
         self,
         message: proto::OnTypeFormattingResponse,
-        project: ModelHandle<Project>,
+        _: ModelHandle<Project>,
         _: ModelHandle<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> Result<ProjectTransaction> {
-        let message = message
-            .transaction
-            .ok_or_else(|| anyhow!("missing transaction"))?;
-        project
-            .update(&mut cx, |project, cx| {
-                project.deserialize_project_transaction(message, self.push_to_history, cx)
-            })
-            .await
+        _: AsyncAppContext,
+    ) -> Result<Option<Transaction>> {
+        let Some(transaction) = message.transaction else { return Ok(None) };
+        Ok(Some(language::proto::deserialize_transaction(transaction)?))
     }
 
     fn buffer_id_from_proto(message: &proto::OnTypeFormatting) -> u64 {

crates/project/src/project.rs 🔗

@@ -4040,9 +4040,8 @@ impl Project {
         buffer: ModelHandle<Buffer>,
         position: Anchor,
         trigger: String,
-        push_to_history: bool,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<ProjectTransaction>> {
+    ) -> Task<Result<Option<Transaction>>> {
         if self.is_local() {
             cx.spawn(|this, mut cx| async move {
                 // Do not allow multiple concurrent formatting requests for the
@@ -4071,7 +4070,7 @@ impl Project {
                     .await?;
                 this.update(&mut cx, |this, cx| {
                     let position = position.to_point_utf16(buffer.read(cx));
-                    this.on_type_format(buffer, position, trigger, cx)
+                    this.on_type_format(buffer, position, trigger, false, cx)
                 })
                 .await
             })
@@ -4084,16 +4083,13 @@ impl Project {
                 trigger,
                 version: serialize_version(&buffer.read(cx).version()),
             };
-            cx.spawn(|this, mut cx| async move {
-                let response = client
+            cx.spawn(|_, _| async move {
+                client
                     .request(request)
                     .await?
                     .transaction
-                    .ok_or_else(|| anyhow!("missing transaction"))?;
-                this.update(&mut cx, |this, cx| {
-                    this.deserialize_project_transaction(response, push_to_history, cx)
-                })
-                .await
+                    .map(language::proto::deserialize_transaction)
+                    .transpose()
             })
         } else {
             Task::ready(Err(anyhow!("project does not have a remote id")))
@@ -4108,7 +4104,7 @@ impl Project {
         _: Arc<CachedLspAdapter>,
         language_server: Arc<LanguageServer>,
         cx: &mut AsyncAppContext,
-    ) -> Result<ProjectTransaction> {
+    ) -> Result<Option<Transaction>> {
         let edits = this
             .update(cx, |this, cx| {
                 this.edits_from_lsp(
@@ -4139,12 +4135,7 @@ impl Project {
             }
         });
 
-        let mut project_transaction = ProjectTransaction::default();
-        if let Some(transaction) = transaction {
-            project_transaction.0.insert(buffer_to_edit, transaction);
-        }
-
-        Ok(project_transaction)
+        Ok(transaction)
     }
 
     async fn deserialize_workspace_edit(
@@ -4317,8 +4308,9 @@ impl Project {
         buffer: ModelHandle<Buffer>,
         position: T,
         trigger: String,
+        push_to_history: bool,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<ProjectTransaction>> {
+    ) -> Task<Result<Option<Transaction>>> {
         let tab_size = buffer.read_with(cx, |buffer, cx| {
             let language_name = buffer.language().map(|language| language.name());
             language_settings(language_name.as_deref(), cx).tab_size
@@ -4330,7 +4322,7 @@ impl Project {
                 position,
                 trigger,
                 options: lsp_command::lsp_formatting_options(tab_size.get()).into(),
-                push_to_history: true,
+                push_to_history,
             },
             cx,
         )
@@ -5912,7 +5904,6 @@ impl Project {
         _: Arc<Client>,
         mut cx: AsyncAppContext,
     ) -> Result<proto::OnTypeFormattingResponse> {
-        let sender_id = envelope.original_sender_id()?;
         let on_type_formatting = this.update(&mut cx, |this, cx| {
             let buffer = this
                 .opened_buffers
@@ -5928,18 +5919,15 @@ impl Project {
                 buffer,
                 position,
                 envelope.payload.trigger.clone(),
-                false,
                 cx,
             ))
         })?;
 
-        let project_transaction = on_type_formatting.await?;
-        let project_transaction = this.update(&mut cx, |this, cx| {
-            this.serialize_project_transaction_for_peer(project_transaction, sender_id, cx)
-        });
-        Ok(proto::OnTypeFormattingResponse {
-            transaction: Some(project_transaction),
-        })
+        let transaction = on_type_formatting
+            .await?
+            .as_ref()
+            .map(language::proto::serialize_transaction);
+        Ok(proto::OnTypeFormattingResponse { transaction })
     }
 
     async fn handle_lsp_command<T: LspCommand>(

crates/rpc/proto/zed.proto 🔗

@@ -682,7 +682,7 @@ message OnTypeFormatting {
 }
 
 message OnTypeFormattingResponse {
-    ProjectTransaction transaction = 1;
+    Transaction transaction = 1;
 }
 
 message PerformRenameResponse {

crates/rpc/src/rpc.rs 🔗

@@ -6,4 +6,4 @@ pub use conn::Connection;
 pub use peer::*;
 mod macros;
 
-pub const PROTOCOL_VERSION: u32 = 55;
+pub const PROTOCOL_VERSION: u32 = 56;