Fix renames over language server for SSH remoting (#17897)

Conrad Irwin , Mikayla , and Max created

Release Notes:

- ssh remoting: Fix rename over language server

---------

Co-authored-by: Mikayla <mikayla@zed.dev>
Co-authored-by: Max <max@zed.dev>

Change summary

Cargo.lock                                                    |   1 
crates/assistant/src/context.rs                               |   6 
crates/call/src/room.rs                                       |   2 
crates/channel/src/channel_buffer.rs                          |   6 
crates/collab/src/db/queries/projects.rs                      |   5 
crates/collab/src/db/tests/db_tests.rs                        |   6 
crates/collab/src/rpc.rs                                      |   1 
crates/collab/src/tests/editor_tests.rs                       |  16 
crates/collab/src/tests/integration_tests.rs                  |  62 
crates/collab/src/tests/random_project_collaboration_tests.rs |   2 
crates/collab_ui/src/chat_panel/message_editor.rs             |   4 
crates/copilot/src/copilot.rs                                 |   9 
crates/editor/src/editor_tests.rs                             |  14 
crates/editor/src/git/blame.rs                                |   4 
crates/editor/src/inlay_hint_cache.rs                         |  12 
crates/editor/src/test/editor_lsp_test_context.rs             |   2 
crates/extension/src/extension_store_test.rs                  |   9 
crates/language/src/buffer.rs                                 |  32 
crates/language/src/buffer_tests.rs                           |  20 
crates/language/src/language.rs                               |  21 
crates/language/src/language_registry.rs                      | 143 +-
crates/language_tools/src/lsp_log_tests.rs                    |   2 
crates/multi_buffer/src/multi_buffer.rs                       |  32 
crates/project/src/buffer_store.rs                            |  10 
crates/project/src/lsp_command.rs                             |  26 
crates/project/src/lsp_ext_command.rs                         |   2 
crates/project/src/lsp_store.rs                               |   7 
crates/project/src/project.rs                                 |  24 
crates/project/src/project_tests.rs                           | 236 ++--
crates/project_symbols/src/project_symbols.rs                 |   2 
crates/proto/proto/zed.proto                                  |   1 
crates/remote_server/Cargo.toml                               |   2 
crates/remote_server/src/headless_project.rs                  |  43 
crates/remote_server/src/remote_editing_tests.rs              |  70 +
34 files changed, 505 insertions(+), 329 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -9058,6 +9058,7 @@ dependencies = [
  "http_client",
  "language",
  "log",
+ "lsp",
  "node_runtime",
  "project",
  "remote",

crates/assistant/src/context.rs 🔗

@@ -997,14 +997,14 @@ impl Context {
     fn handle_buffer_event(
         &mut self,
         _: Model<Buffer>,
-        event: &language::Event,
+        event: &language::BufferEvent,
         cx: &mut ModelContext<Self>,
     ) {
         match event {
-            language::Event::Operation(operation) => cx.emit(ContextEvent::Operation(
+            language::BufferEvent::Operation(operation) => cx.emit(ContextEvent::Operation(
                 ContextOperation::BufferOperation(operation.clone()),
             )),
-            language::Event::Edited => {
+            language::BufferEvent::Edited => {
                 self.count_remaining_tokens(cx);
                 self.reparse(cx);
                 // Use `inclusive = true` to invalidate a step when an edit occurs

crates/call/src/room.rs 🔗

@@ -1200,6 +1200,7 @@ impl Room {
                 room_id: self.id(),
                 worktrees: vec![],
                 dev_server_project_id: Some(dev_server_project_id.0),
+                is_ssh_project: false,
             })
         } else {
             if let Some(project_id) = project.read(cx).remote_id() {
@@ -1210,6 +1211,7 @@ impl Room {
                 room_id: self.id(),
                 worktrees: project.read(cx).worktree_metadata_protos(cx),
                 dev_server_project_id: None,
+                is_ssh_project: project.read(cx).is_via_ssh(),
             })
         };
 

crates/channel/src/channel_buffer.rs 🔗

@@ -171,11 +171,11 @@ impl ChannelBuffer {
     fn on_buffer_update(
         &mut self,
         _: Model<language::Buffer>,
-        event: &language::Event,
+        event: &language::BufferEvent,
         cx: &mut ModelContext<Self>,
     ) {
         match event {
-            language::Event::Operation(operation) => {
+            language::BufferEvent::Operation(operation) => {
                 if *ZED_ALWAYS_ACTIVE {
                     if let language::Operation::UpdateSelections { selections, .. } = operation {
                         if selections.is_empty() {
@@ -191,7 +191,7 @@ impl ChannelBuffer {
                     })
                     .log_err();
             }
-            language::Event::Edited => {
+            language::BufferEvent::Edited => {
                 cx.emit(ChannelBufferEvent::BufferEdited);
             }
             _ => {}

crates/collab/src/db/queries/projects.rs 🔗

@@ -30,6 +30,7 @@ impl Database {
         room_id: RoomId,
         connection: ConnectionId,
         worktrees: &[proto::WorktreeMetadata],
+        is_ssh_project: bool,
         dev_server_project_id: Option<DevServerProjectId>,
     ) -> Result<TransactionGuard<(ProjectId, proto::Room)>> {
         self.room_transaction(room_id, |tx| async move {
@@ -121,12 +122,14 @@ impl Database {
                 .await?;
             }
 
+            let replica_id = if is_ssh_project { 1 } else { 0 };
+
             project_collaborator::ActiveModel {
                 project_id: ActiveValue::set(project.id),
                 connection_id: ActiveValue::set(connection.id as i32),
                 connection_server_id: ActiveValue::set(ServerId(connection.owner_id as i32)),
                 user_id: ActiveValue::set(participant.user_id),
-                replica_id: ActiveValue::set(ReplicaId(0)),
+                replica_id: ActiveValue::set(ReplicaId(replica_id)),
                 is_host: ActiveValue::set(true),
                 ..Default::default()
             }

crates/collab/src/db/tests/db_tests.rs 🔗

@@ -540,18 +540,18 @@ async fn test_project_count(db: &Arc<Database>) {
         .unwrap();
     assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
 
-    db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[], None)
+    db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[], false, None)
         .await
         .unwrap();
     assert_eq!(db.project_count_excluding_admins().await.unwrap(), 1);
 
-    db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[], None)
+    db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[], false, None)
         .await
         .unwrap();
     assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
 
     // Projects shared by admins aren't counted.
-    db.share_project(room_id, ConnectionId { owner_id, id: 0 }, &[], None)
+    db.share_project(room_id, ConnectionId { owner_id, id: 0 }, &[], false, None)
         .await
         .unwrap();
     assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);

crates/collab/src/rpc.rs 🔗

@@ -1996,6 +1996,7 @@ async fn share_project(
             RoomId::from_proto(request.room_id),
             session.connection_id,
             &request.worktrees,
+            request.is_ssh_project,
             request
                 .dev_server_project_id
                 .map(DevServerProjectId::from_proto),

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

@@ -284,7 +284,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
     let active_call_a = cx_a.read(ActiveCall::global);
 
     client_a.language_registry().add(rust_lang());
-    let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
+    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
         "Rust",
         FakeLspAdapter {
             capabilities: lsp::ServerCapabilities {
@@ -552,7 +552,7 @@ async fn test_collaborating_with_code_actions(
     client_a.language_registry().add(rust_lang());
     let mut fake_language_servers = client_a
         .language_registry()
-        .register_fake_lsp_adapter("Rust", FakeLspAdapter::default());
+        .register_fake_lsp("Rust", FakeLspAdapter::default());
 
     client_a
         .fs()
@@ -757,7 +757,7 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
 
     // Set up a fake language server.
     client_a.language_registry().add(rust_lang());
-    let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
+    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
         "Rust",
         FakeLspAdapter {
             capabilities: lsp::ServerCapabilities {
@@ -982,7 +982,7 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
     cx_b.update(editor::init);
 
     client_a.language_registry().add(rust_lang());
-    let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
+    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
         "Rust",
         FakeLspAdapter {
             name: "the-language-server",
@@ -1268,7 +1268,7 @@ async fn test_on_input_format_from_host_to_guest(
     let active_call_a = cx_a.read(ActiveCall::global);
 
     client_a.language_registry().add(rust_lang());
-    let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
+    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
         "Rust",
         FakeLspAdapter {
             capabilities: lsp::ServerCapabilities {
@@ -1388,7 +1388,7 @@ async fn test_on_input_format_from_guest_to_host(
     let active_call_a = cx_a.read(ActiveCall::global);
 
     client_a.language_registry().add(rust_lang());
-    let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
+    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
         "Rust",
         FakeLspAdapter {
             capabilities: lsp::ServerCapabilities {
@@ -1545,7 +1545,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
 
     client_a.language_registry().add(rust_lang());
     client_b.language_registry().add(rust_lang());
-    let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
+    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
         "Rust",
         FakeLspAdapter {
             capabilities: lsp::ServerCapabilities {
@@ -1807,7 +1807,7 @@ async fn test_inlay_hint_refresh_is_forwarded(
 
     client_a.language_registry().add(rust_lang());
     client_b.language_registry().add(rust_lang());
-    let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
+    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
         "Rust",
         FakeLspAdapter {
             capabilities: lsp::ServerCapabilities {

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

@@ -3859,7 +3859,7 @@ async fn test_collaborating_with_diagnostics(
     )));
     let mut fake_language_servers = client_a
         .language_registry()
-        .register_fake_lsp_adapter("Rust", Default::default());
+        .register_fake_lsp("Rust", Default::default());
 
     // Share a project as client A
     client_a
@@ -4126,7 +4126,7 @@ async fn test_collaborating_with_lsp_progress_updates_and_diagnostics_ordering(
         .await;
 
     client_a.language_registry().add(rust_lang());
-    let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
+    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
         "Rust",
         FakeLspAdapter {
             disk_based_diagnostics_progress_token: Some("the-disk-based-token".into()),
@@ -4349,7 +4349,7 @@ async fn test_formatting_buffer(
     client_a.language_registry().add(rust_lang());
     let mut fake_language_servers = client_a
         .language_registry()
-        .register_fake_lsp_adapter("Rust", FakeLspAdapter::default());
+        .register_fake_lsp("Rust", FakeLspAdapter::default());
 
     // Here we insert a fake tree with a directory that exists on disk. This is needed
     // because later we'll invoke a command, which requires passing a working directory
@@ -4460,7 +4460,7 @@ async fn test_prettier_formatting_buffer(
         },
         Some(tree_sitter_rust::language()),
     )));
-    let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
+    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
         "TypeScript",
         FakeLspAdapter {
             prettier_plugins: vec![test_plugin],
@@ -4576,7 +4576,7 @@ async fn test_definition(
 
     let mut fake_language_servers = client_a
         .language_registry()
-        .register_fake_lsp_adapter("Rust", Default::default());
+        .register_fake_lsp("Rust", Default::default());
     client_a.language_registry().add(rust_lang());
 
     client_a
@@ -4712,7 +4712,7 @@ async fn test_references(
     let active_call_a = cx_a.read(ActiveCall::global);
 
     client_a.language_registry().add(rust_lang());
-    let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
+    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
         "Rust",
         FakeLspAdapter {
             name: "my-fake-lsp-adapter",
@@ -4983,7 +4983,7 @@ async fn test_document_highlights(
 
     let mut fake_language_servers = client_a
         .language_registry()
-        .register_fake_lsp_adapter("Rust", Default::default());
+        .register_fake_lsp("Rust", Default::default());
     client_a.language_registry().add(rust_lang());
 
     let (project_a, worktree_id) = client_a.build_local_project("/root-1", cx_a).await;
@@ -5079,28 +5079,30 @@ async fn test_lsp_hover(
 
     client_a.language_registry().add(rust_lang());
     let language_server_names = ["rust-analyzer", "CrabLang-ls"];
-    let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
-        "Rust",
-        FakeLspAdapter {
-            name: "rust-analyzer",
-            capabilities: lsp::ServerCapabilities {
-                hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
-                ..lsp::ServerCapabilities::default()
+    let mut language_servers = [
+        client_a.language_registry().register_fake_lsp(
+            "Rust",
+            FakeLspAdapter {
+                name: "rust-analyzer",
+                capabilities: lsp::ServerCapabilities {
+                    hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
+                    ..lsp::ServerCapabilities::default()
+                },
+                ..FakeLspAdapter::default()
             },
-            ..FakeLspAdapter::default()
-        },
-    );
-    let _other_server = client_a.language_registry().register_fake_lsp_adapter(
-        "Rust",
-        FakeLspAdapter {
-            name: "CrabLang-ls",
-            capabilities: lsp::ServerCapabilities {
-                hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
-                ..lsp::ServerCapabilities::default()
+        ),
+        client_a.language_registry().register_fake_lsp(
+            "Rust",
+            FakeLspAdapter {
+                name: "CrabLang-ls",
+                capabilities: lsp::ServerCapabilities {
+                    hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
+                    ..lsp::ServerCapabilities::default()
+                },
+                ..FakeLspAdapter::default()
             },
-            ..FakeLspAdapter::default()
-        },
-    );
+        ),
+    ];
 
     let (project_a, worktree_id) = client_a.build_local_project("/root-1", cx_a).await;
     let project_id = active_call_a
@@ -5115,7 +5117,7 @@ async fn test_lsp_hover(
 
     let mut servers_with_hover_requests = HashMap::default();
     for i in 0..language_server_names.len() {
-        let new_server = fake_language_servers.next().await.unwrap_or_else(|| {
+        let new_server = language_servers[i].next().await.unwrap_or_else(|| {
             panic!(
                 "Failed to get language server #{i} with name {}",
                 &language_server_names[i]
@@ -5260,7 +5262,7 @@ async fn test_project_symbols(
     client_a.language_registry().add(rust_lang());
     let mut fake_language_servers = client_a
         .language_registry()
-        .register_fake_lsp_adapter("Rust", Default::default());
+        .register_fake_lsp("Rust", Default::default());
 
     client_a
         .fs()
@@ -5362,7 +5364,7 @@ async fn test_open_buffer_while_getting_definition_pointing_to_it(
     client_a.language_registry().add(rust_lang());
     let mut fake_language_servers = client_a
         .language_registry()
-        .register_fake_lsp_adapter("Rust", Default::default());
+        .register_fake_lsp("Rust", Default::default());
 
     client_a
         .fs()

crates/collab_ui/src/chat_panel/message_editor.rs 🔗

@@ -228,10 +228,10 @@ impl MessageEditor {
     fn on_buffer_event(
         &mut self,
         buffer: Model<Buffer>,
-        event: &language::Event,
+        event: &language::BufferEvent,
         cx: &mut ViewContext<Self>,
     ) {
-        if let language::Event::Reparsed | language::Event::Edited = event {
+        if let language::BufferEvent::Reparsed | language::BufferEvent::Edited = event {
             let buffer = buffer.read(cx).snapshot();
             self.mentions_task = Some(cx.spawn(|this, cx| async move {
                 cx.background_executor()

crates/copilot/src/copilot.rs 🔗

@@ -691,17 +691,17 @@ impl Copilot {
     fn handle_buffer_event(
         &mut self,
         buffer: Model<Buffer>,
-        event: &language::Event,
+        event: &language::BufferEvent,
         cx: &mut ModelContext<Self>,
     ) -> Result<()> {
         if let Ok(server) = self.server.as_running() {
             if let Some(registered_buffer) = server.registered_buffers.get_mut(&buffer.entity_id())
             {
                 match event {
-                    language::Event::Edited => {
+                    language::BufferEvent::Edited => {
                         drop(registered_buffer.report_changes(&buffer, cx));
                     }
-                    language::Event::Saved => {
+                    language::BufferEvent::Saved => {
                         server
                             .lsp
                             .notify::<lsp::notification::DidSaveTextDocument>(
@@ -713,7 +713,8 @@ impl Copilot {
                                 },
                             )?;
                     }
-                    language::Event::FileHandleChanged | language::Event::LanguageChanged => {
+                    language::BufferEvent::FileHandleChanged
+                    | language::BufferEvent::LanguageChanged => {
                         let new_language_id = id_for_language(buffer.read(cx).language());
                         let new_uri = uri_for_buffer(&buffer, cx);
                         if new_uri != registered_buffer.uri

crates/editor/src/editor_tests.rs 🔗

@@ -6205,7 +6205,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
 
     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_adapter(
+    let mut fake_servers = language_registry.register_fake_lsp(
         "Rust",
         FakeLspAdapter {
             capabilities: lsp::ServerCapabilities {
@@ -6361,7 +6361,7 @@ async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
 
     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_adapter(
+    let mut fake_servers = language_registry.register_fake_lsp(
         "Rust",
         FakeLspAdapter {
             capabilities: lsp::ServerCapabilities {
@@ -6557,7 +6557,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
 
     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_adapter(
+    let mut fake_servers = language_registry.register_fake_lsp(
         "Rust",
         FakeLspAdapter {
             capabilities: lsp::ServerCapabilities {
@@ -6708,7 +6708,7 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
             ..PrettierSettings::default()
         });
     });
-    let mut fake_servers = language_registry.register_fake_lsp_adapter(
+    let mut fake_servers = language_registry.register_fake_lsp(
         "Rust",
         FakeLspAdapter {
             capabilities: lsp::ServerCapabilities {
@@ -9488,7 +9488,7 @@ async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
         },
         Some(tree_sitter_rust::language()),
     )));
-    let mut fake_servers = language_registry.register_fake_lsp_adapter(
+    let mut fake_servers = language_registry.register_fake_lsp(
         "Rust",
         FakeLspAdapter {
             capabilities: lsp::ServerCapabilities {
@@ -9601,7 +9601,7 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::Test
         },
         Some(tree_sitter_rust::language()),
     )));
-    let mut fake_servers = language_registry.register_fake_lsp_adapter(
+    let mut fake_servers = language_registry.register_fake_lsp(
         "Rust",
         FakeLspAdapter {
             name: language_server_name,
@@ -9945,7 +9945,7 @@ async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
     });
 
     let test_plugin = "test_plugin";
-    let _ = language_registry.register_fake_lsp_adapter(
+    let _ = language_registry.register_fake_lsp(
         "TypeScript",
         FakeLspAdapter {
             prettier_plugins: vec![test_plugin],

crates/editor/src/git/blame.rs 🔗

@@ -121,12 +121,12 @@ impl GitBlame {
         );
 
         let buffer_subscriptions = cx.subscribe(&buffer, |this, buffer, event, cx| match event {
-            language::Event::DirtyChanged => {
+            language::BufferEvent::DirtyChanged => {
                 if !buffer.read(cx).is_dirty() {
                     this.generate(cx);
                 }
             }
-            language::Event::Edited => {
+            language::BufferEvent::Edited => {
                 this.regenerate_on_edit(cx);
             }
             _ => {}

crates/editor/src/inlay_hint_cache.rs 🔗

@@ -1577,7 +1577,7 @@ pub mod tests {
                 },
                 Some(tree_sitter_rust::language()),
             )));
-            let fake_servers = language_registry.register_fake_lsp_adapter(
+            let fake_servers = language_registry.register_fake_lsp(
                 name,
                 FakeLspAdapter {
                     name,
@@ -2273,7 +2273,7 @@ pub mod tests {
 
         let language_registry = project.read_with(cx, |project, _| project.languages().clone());
         language_registry.add(crate::editor_tests::rust_lang());
-        let mut fake_servers = language_registry.register_fake_lsp_adapter(
+        let mut fake_servers = language_registry.register_fake_lsp(
             "Rust",
             FakeLspAdapter {
                 capabilities: lsp::ServerCapabilities {
@@ -2569,7 +2569,7 @@ pub mod tests {
         let language_registry = project.read_with(cx, |project, _| project.languages().clone());
         let language = crate::editor_tests::rust_lang();
         language_registry.add(language);
-        let mut fake_servers = language_registry.register_fake_lsp_adapter(
+        let mut fake_servers = language_registry.register_fake_lsp(
             "Rust",
             FakeLspAdapter {
                 capabilities: lsp::ServerCapabilities {
@@ -2919,7 +2919,7 @@ pub mod tests {
 
         let language_registry = project.read_with(cx, |project, _| project.languages().clone());
         language_registry.add(crate::editor_tests::rust_lang());
-        let mut fake_servers = language_registry.register_fake_lsp_adapter(
+        let mut fake_servers = language_registry.register_fake_lsp(
             "Rust",
             FakeLspAdapter {
                 capabilities: lsp::ServerCapabilities {
@@ -3148,7 +3148,7 @@ pub mod tests {
 
         let language_registry = project.read_with(cx, |project, _| project.languages().clone());
         language_registry.add(crate::editor_tests::rust_lang());
-        let mut fake_servers = language_registry.register_fake_lsp_adapter(
+        let mut fake_servers = language_registry.register_fake_lsp(
             "Rust",
             FakeLspAdapter {
                 capabilities: lsp::ServerCapabilities {
@@ -3389,7 +3389,7 @@ pub mod tests {
 
         let language_registry = project.read_with(cx, |project, _| project.languages().clone());
         language_registry.add(crate::editor_tests::rust_lang());
-        let mut fake_servers = language_registry.register_fake_lsp_adapter(
+        let mut fake_servers = language_registry.register_fake_lsp(
             "Rust",
             FakeLspAdapter {
                 capabilities: lsp::ServerCapabilities {

crates/editor/src/test/editor_lsp_test_context.rs 🔗

@@ -57,7 +57,7 @@ impl EditorLspTestContext {
         let project = Project::test(app_state.fs.clone(), [], cx).await;
 
         let language_registry = project.read_with(cx, |project, _| project.languages().clone());
-        let mut fake_servers = language_registry.register_fake_lsp_adapter(
+        let mut fake_servers = language_registry.register_fake_lsp(
             language.name(),
             FakeLspAdapter {
                 capabilities,

crates/extension/src/extension_store_test.rs 🔗

@@ -609,7 +609,14 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
         .await
         .unwrap();
 
-    let mut fake_servers = language_registry.fake_language_servers("Gleam".into());
+    let mut fake_servers = language_registry.register_fake_language_server(
+        LanguageServerName("gleam".into()),
+        lsp::ServerCapabilities {
+            completion_provider: Some(Default::default()),
+            ..Default::default()
+        },
+        None,
+    );
 
     let buffer = project
         .update(cx, |project, cx| {

crates/language/src/buffer.rs 🔗

@@ -302,7 +302,7 @@ pub enum Operation {
 
 /// An event that occurs in a buffer.
 #[derive(Clone, Debug, PartialEq)]
-pub enum Event {
+pub enum BufferEvent {
     /// The buffer was changed in a way that must be
     /// propagated to its other replicas.
     Operation(Operation),
@@ -809,7 +809,7 @@ impl Buffer {
         self.syntax_map.lock().clear();
         self.language = language;
         self.reparse(cx);
-        cx.emit(Event::LanguageChanged);
+        cx.emit(BufferEvent::LanguageChanged);
     }
 
     /// Assign a language registry to the buffer. This allows the buffer to retrieve
@@ -827,7 +827,7 @@ impl Buffer {
     /// Assign the buffer a new [Capability].
     pub fn set_capability(&mut self, capability: Capability, cx: &mut ModelContext<Self>) {
         self.capability = capability;
-        cx.emit(Event::CapabilityChanged)
+        cx.emit(BufferEvent::CapabilityChanged)
     }
 
     /// This method is called to signal that the buffer has been saved.
@@ -842,13 +842,13 @@ impl Buffer {
             .set((self.saved_version().clone(), false));
         self.has_conflict = false;
         self.saved_mtime = mtime;
-        cx.emit(Event::Saved);
+        cx.emit(BufferEvent::Saved);
         cx.notify();
     }
 
     /// This method is called to signal that the buffer has been discarded.
     pub fn discarded(&mut self, cx: &mut ModelContext<Self>) {
-        cx.emit(Event::Discarded);
+        cx.emit(BufferEvent::Discarded);
         cx.notify();
     }
 
@@ -911,7 +911,7 @@ impl Buffer {
             .set((self.saved_version.clone(), false));
         self.text.set_line_ending(line_ending);
         self.saved_mtime = mtime;
-        cx.emit(Event::Reloaded);
+        cx.emit(BufferEvent::Reloaded);
         cx.notify();
     }
 
@@ -929,7 +929,7 @@ impl Buffer {
                 if !old_file.is_deleted() {
                     file_changed = true;
                     if !self.is_dirty() {
-                        cx.emit(Event::DirtyChanged);
+                        cx.emit(BufferEvent::DirtyChanged);
                     }
                 }
             } else {
@@ -949,7 +949,7 @@ impl Buffer {
         self.file = Some(new_file);
         if file_changed {
             self.non_text_state_update_count += 1;
-            cx.emit(Event::FileHandleChanged);
+            cx.emit(BufferEvent::FileHandleChanged);
             cx.notify();
         }
     }
@@ -974,7 +974,7 @@ impl Buffer {
                 recalc_task.await;
                 buffer
                     .update(&mut cx, |_, cx| {
-                        cx.emit(Event::DiffBaseChanged);
+                        cx.emit(BufferEvent::DiffBaseChanged);
                     })
                     .ok();
             })
@@ -1003,7 +1003,7 @@ impl Buffer {
             this.update(&mut cx, |this, cx| {
                 this.git_diff = buffer_diff;
                 this.non_text_state_update_count += 1;
-                cx.emit(Event::DiffUpdated);
+                cx.emit(BufferEvent::DiffUpdated);
             })
             .ok();
         }))
@@ -1142,7 +1142,7 @@ impl Buffer {
         self.syntax_map.lock().did_parse(syntax_snapshot);
         self.request_autoindent(cx);
         self.parse_status.0.send(ParseStatus::Idle).unwrap();
-        cx.emit(Event::Reparsed);
+        cx.emit(BufferEvent::Reparsed);
         cx.notify();
     }
 
@@ -1900,9 +1900,9 @@ impl Buffer {
 
         self.reparse(cx);
 
-        cx.emit(Event::Edited);
+        cx.emit(BufferEvent::Edited);
         if was_dirty != self.is_dirty() {
-            cx.emit(Event::DirtyChanged);
+            cx.emit(BufferEvent::DirtyChanged);
         }
         cx.notify();
     }
@@ -2106,12 +2106,12 @@ impl Buffer {
             self.non_text_state_update_count += 1;
             self.text.lamport_clock.observe(lamport_timestamp);
             cx.notify();
-            cx.emit(Event::DiagnosticsUpdated);
+            cx.emit(BufferEvent::DiagnosticsUpdated);
         }
     }
 
     fn send_operation(&mut self, operation: Operation, cx: &mut ModelContext<Self>) {
-        cx.emit(Event::Operation(operation));
+        cx.emit(BufferEvent::Operation(operation));
     }
 
     /// Removes the selections for a given peer.
@@ -2300,7 +2300,7 @@ impl Buffer {
     }
 }
 
-impl EventEmitter<Event> for Buffer {}
+impl EventEmitter<BufferEvent> for Buffer {}
 
 impl Deref for Buffer {
     type Target = TextBuffer;

crates/language/src/buffer_tests.rs 🔗

@@ -275,7 +275,7 @@ fn test_edit_events(cx: &mut gpui::AppContext) {
         |buffer, cx| {
             let buffer_1_events = buffer_1_events.clone();
             cx.subscribe(&buffer1, move |_, _, event, _| match event.clone() {
-                Event::Operation(op) => buffer1_ops.lock().push(op),
+                BufferEvent::Operation(op) => buffer1_ops.lock().push(op),
                 event => buffer_1_events.lock().push(event),
             })
             .detach();
@@ -313,15 +313,15 @@ fn test_edit_events(cx: &mut gpui::AppContext) {
     assert_eq!(
         mem::take(&mut *buffer_1_events.lock()),
         vec![
-            Event::Edited,
-            Event::DirtyChanged,
-            Event::Edited,
-            Event::Edited,
+            BufferEvent::Edited,
+            BufferEvent::DirtyChanged,
+            BufferEvent::Edited,
+            BufferEvent::Edited,
         ]
     );
     assert_eq!(
         mem::take(&mut *buffer_2_events.lock()),
-        vec![Event::Edited, Event::DirtyChanged]
+        vec![BufferEvent::Edited, BufferEvent::DirtyChanged]
     );
 
     buffer1.update(cx, |buffer, cx| {
@@ -336,11 +336,11 @@ fn test_edit_events(cx: &mut gpui::AppContext) {
     });
     assert_eq!(
         mem::take(&mut *buffer_1_events.lock()),
-        vec![Event::Edited, Event::DirtyChanged,]
+        vec![BufferEvent::Edited, BufferEvent::DirtyChanged,]
     );
     assert_eq!(
         mem::take(&mut *buffer_2_events.lock()),
-        vec![Event::Edited, Event::DirtyChanged]
+        vec![BufferEvent::Edited, BufferEvent::DirtyChanged]
     );
 }
 
@@ -2411,7 +2411,7 @@ fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
             buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200)));
             let network = network.clone();
             cx.subscribe(&cx.handle(), move |buffer, _, event, _| {
-                if let Event::Operation(op) = event {
+                if let BufferEvent::Operation(op) = event {
                     network
                         .lock()
                         .broadcast(buffer.replica_id(), vec![proto::serialize_operation(op)]);
@@ -2539,7 +2539,7 @@ fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
                     new_buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200)));
                     let network = network.clone();
                     cx.subscribe(&cx.handle(), move |buffer, _, event, _| {
-                        if let Event::Operation(op) = event {
+                        if let BufferEvent::Operation(op) = event {
                             network.lock().broadcast(
                                 buffer.replica_id(),
                                 vec![proto::serialize_operation(op)],

crates/language/src/language.rs 🔗

@@ -270,11 +270,6 @@ impl CachedLspAdapter {
             .cloned()
             .unwrap_or_else(|| language_name.lsp_id())
     }
-
-    #[cfg(any(test, feature = "test-support"))]
-    fn as_fake(&self) -> Option<&FakeLspAdapter> {
-        self.adapter.as_fake()
-    }
 }
 
 /// [`LspAdapterDelegate`] allows [`LspAdapter]` implementations to interface with the application
@@ -508,11 +503,6 @@ pub trait LspAdapter: 'static + Send + Sync {
     fn language_ids(&self) -> HashMap<String, String> {
         Default::default()
     }
-
-    #[cfg(any(test, feature = "test-support"))]
-    fn as_fake(&self) -> Option<&FakeLspAdapter> {
-        None
-    }
 }
 
 async fn try_fetch_server_binary<L: LspAdapter + 'static + Send + Sync + ?Sized>(
@@ -751,12 +741,13 @@ where
 pub struct FakeLspAdapter {
     pub name: &'static str,
     pub initialization_options: Option<Value>,
-    pub capabilities: lsp::ServerCapabilities,
-    pub initializer: Option<Box<dyn 'static + Send + Sync + Fn(&mut lsp::FakeLanguageServer)>>,
+    pub prettier_plugins: Vec<&'static str>,
     pub disk_based_diagnostics_progress_token: Option<String>,
     pub disk_based_diagnostics_sources: Vec<String>,
-    pub prettier_plugins: Vec<&'static str>,
     pub language_server_binary: LanguageServerBinary,
+
+    pub capabilities: lsp::ServerCapabilities,
+    pub initializer: Option<Box<dyn 'static + Send + Sync + Fn(&mut lsp::FakeLanguageServer)>>,
 }
 
 /// Configuration of handling bracket pairs for a given language.
@@ -1717,10 +1708,6 @@ impl LspAdapter for FakeLspAdapter {
     ) -> Result<Option<Value>> {
         Ok(self.initialization_options.clone())
     }
-
-    fn as_fake(&self) -> Option<&FakeLspAdapter> {
-        Some(self)
-    }
 }
 
 fn get_capture_indices(query: &Query, captures: &mut [(&str, &mut Option<u32>)]) {

crates/language/src/language_registry.rs 🔗

@@ -99,10 +99,15 @@ struct LanguageRegistryState {
     reload_count: usize,
 
     #[cfg(any(test, feature = "test-support"))]
-    fake_server_txs: HashMap<
-        LanguageName,
-        Vec<futures::channel::mpsc::UnboundedSender<lsp::FakeLanguageServer>>,
-    >,
+    fake_server_entries: HashMap<LanguageServerName, FakeLanguageServerEntry>,
+}
+
+#[cfg(any(test, feature = "test-support"))]
+pub struct FakeLanguageServerEntry {
+    pub capabilities: lsp::ServerCapabilities,
+    pub initializer: Option<Box<dyn 'static + Send + Sync + Fn(&mut lsp::FakeLanguageServer)>>,
+    pub tx: futures::channel::mpsc::UnboundedSender<lsp::FakeLanguageServer>,
+    pub _server: Option<lsp::FakeLanguageServer>,
 }
 
 #[derive(Clone, Debug, PartialEq, Eq)]
@@ -220,7 +225,7 @@ impl LanguageRegistry {
                 reload_count: 0,
 
                 #[cfg(any(test, feature = "test-support"))]
-                fake_server_txs: Default::default(),
+                fake_server_entries: Default::default(),
             }),
             language_server_download_dir: None,
             lsp_binary_status_tx: Default::default(),
@@ -330,34 +335,63 @@ impl LanguageRegistry {
             .push(CachedLspAdapter::new(adapter));
     }
 
+    /// Register a fake language server and adapter
+    /// The returned channel receives a new instance of the language server every time it is started
     #[cfg(any(feature = "test-support", test))]
-    pub fn register_fake_lsp_adapter(
+    pub fn register_fake_lsp(
         &self,
         language_name: impl Into<LanguageName>,
-        adapter: crate::FakeLspAdapter,
+        mut adapter: crate::FakeLspAdapter,
     ) -> futures::channel::mpsc::UnboundedReceiver<lsp::FakeLanguageServer> {
         let language_name = language_name.into();
+        let adapter_name = LanguageServerName(adapter.name.into());
+        let capabilities = adapter.capabilities.clone();
+        let initializer = adapter.initializer.take();
         self.state
             .write()
             .lsp_adapters
             .entry(language_name.clone())
             .or_default()
             .push(CachedLspAdapter::new(Arc::new(adapter)));
-        self.fake_language_servers(language_name)
+        self.register_fake_language_server(adapter_name, capabilities, initializer)
     }
 
+    /// Register a fake lsp adapter (without the language server)
+    /// The returned channel receives a new instance of the language server every time it is started
     #[cfg(any(feature = "test-support", test))]
-    pub fn fake_language_servers(
+    pub fn register_fake_lsp_adapter(
         &self,
-        language_name: LanguageName,
-    ) -> futures::channel::mpsc::UnboundedReceiver<lsp::FakeLanguageServer> {
-        let (servers_tx, servers_rx) = futures::channel::mpsc::unbounded();
+        language_name: impl Into<LanguageName>,
+        adapter: crate::FakeLspAdapter,
+    ) {
+        let language_name = language_name.into();
         self.state
             .write()
-            .fake_server_txs
-            .entry(language_name)
+            .lsp_adapters
+            .entry(language_name.clone())
             .or_default()
-            .push(servers_tx);
+            .push(CachedLspAdapter::new(Arc::new(adapter)));
+    }
+
+    /// Register a fake language server (without the adapter)
+    /// The returned channel receives a new instance of the language server every time it is started
+    #[cfg(any(feature = "test-support", test))]
+    pub fn register_fake_language_server(
+        &self,
+        lsp_name: LanguageServerName,
+        capabilities: lsp::ServerCapabilities,
+        initializer: Option<Box<dyn Fn(&mut lsp::FakeLanguageServer) + Send + Sync>>,
+    ) -> futures::channel::mpsc::UnboundedReceiver<lsp::FakeLanguageServer> {
+        let (servers_tx, servers_rx) = futures::channel::mpsc::unbounded();
+        self.state.write().fake_server_entries.insert(
+            lsp_name,
+            FakeLanguageServerEntry {
+                tx: servers_tx,
+                capabilities,
+                initializer,
+                _server: None,
+            },
+        );
         servers_rx
     }
 
@@ -835,8 +869,8 @@ impl LanguageRegistry {
             adapter.name.0
         );
 
-        let download_dir = self
-            .language_server_download_dir
+        let download_dir = &self
+                    .language_server_download_dir
             .clone()
             .ok_or_else(|| anyhow!("language server download directory has not been assigned before starting server"))
             .log_err()?;
@@ -877,52 +911,43 @@ impl LanguageRegistry {
 
                 #[cfg(any(test, feature = "test-support"))]
                 if true {
-                    let capabilities = adapter
-                        .as_fake()
-                        .map(|fake_adapter| fake_adapter.capabilities.clone())
-                        .unwrap_or_else(|| lsp::ServerCapabilities {
-                            completion_provider: Some(Default::default()),
-                            ..Default::default()
-                        });
-
-                    let (server, mut fake_server) = lsp::FakeLanguageServer::new(
-                        server_id,
-                        binary,
-                        adapter.name.0.to_string(),
-                        capabilities,
-                        cx.clone(),
-                    );
-
-                    if let Some(fake_adapter) = adapter.as_fake() {
-                        if let Some(initializer) = &fake_adapter.initializer {
-                            initializer(&mut fake_server);
-                        }
-                    }
+                    if let Some(this) = this.upgrade() {
+                        if let Some(fake_entry) = this
+                            .state
+                            .write()
+                            .fake_server_entries
+                            .get_mut(&adapter.name)
+                        {
+                            let (server, mut fake_server) = lsp::FakeLanguageServer::new(
+                                server_id,
+                                binary,
+                                adapter.name.0.to_string(),
+                                fake_entry.capabilities.clone(),
+                                cx.clone(),
+                            );
+                            fake_entry._server = Some(fake_server.clone());
+
+                            if let Some(initializer) = &fake_entry.initializer {
+                                initializer(&mut fake_server);
+                            }
 
-                    cx.background_executor()
-                        .spawn(async move {
-                            if fake_server
-                                .try_receive_notification::<lsp::notification::Initialized>()
-                                .await
-                                .is_some()
-                            {
-                                if let Some(this) = this.upgrade() {
-                                    if let Some(txs) = this
-                                        .state
-                                        .write()
-                                        .fake_server_txs
-                                        .get_mut(&_language_name_for_tests)
+                            let tx = fake_entry.tx.clone();
+                            cx.background_executor()
+                                .spawn(async move {
+                                    if fake_server
+                                        .try_receive_notification::<lsp::notification::Initialized>(
+                                        )
+                                        .await
+                                        .is_some()
                                     {
-                                        for tx in txs {
-                                            tx.unbounded_send(fake_server.clone()).ok();
-                                        }
+                                        tx.unbounded_send(fake_server.clone()).ok();
                                     }
-                                }
-                            }
-                        })
-                        .detach();
+                                })
+                                .detach();
 
-                    return Ok((server, options));
+                            return Ok((server, options));
+                        }
+                    }
                 }
 
                 drop(this);

crates/language_tools/src/lsp_log_tests.rs 🔗

@@ -45,7 +45,7 @@ async fn test_lsp_logs(cx: &mut TestAppContext) {
         },
         Some(tree_sitter_rust::language()),
     )));
-    let mut fake_rust_servers = language_registry.register_fake_lsp_adapter(
+    let mut fake_rust_servers = language_registry.register_fake_lsp(
         "Rust",
         FakeLspAdapter {
             name: "the-rust-language-server",

crates/multi_buffer/src/multi_buffer.rs 🔗

@@ -1673,31 +1673,33 @@ impl MultiBuffer {
     fn on_buffer_event(
         &mut self,
         buffer: Model<Buffer>,
-        event: &language::Event,
+        event: &language::BufferEvent,
         cx: &mut ModelContext<Self>,
     ) {
         cx.emit(match event {
-            language::Event::Edited => Event::Edited {
+            language::BufferEvent::Edited => Event::Edited {
                 singleton_buffer_edited: true,
             },
-            language::Event::DirtyChanged => Event::DirtyChanged,
-            language::Event::Saved => Event::Saved,
-            language::Event::FileHandleChanged => Event::FileHandleChanged,
-            language::Event::Reloaded => Event::Reloaded,
-            language::Event::DiffBaseChanged => Event::DiffBaseChanged,
-            language::Event::DiffUpdated => Event::DiffUpdated { buffer },
-            language::Event::LanguageChanged => Event::LanguageChanged(buffer.read(cx).remote_id()),
-            language::Event::Reparsed => Event::Reparsed(buffer.read(cx).remote_id()),
-            language::Event::DiagnosticsUpdated => Event::DiagnosticsUpdated,
-            language::Event::Closed => Event::Closed,
-            language::Event::Discarded => Event::Discarded,
-            language::Event::CapabilityChanged => {
+            language::BufferEvent::DirtyChanged => Event::DirtyChanged,
+            language::BufferEvent::Saved => Event::Saved,
+            language::BufferEvent::FileHandleChanged => Event::FileHandleChanged,
+            language::BufferEvent::Reloaded => Event::Reloaded,
+            language::BufferEvent::DiffBaseChanged => Event::DiffBaseChanged,
+            language::BufferEvent::DiffUpdated => Event::DiffUpdated { buffer },
+            language::BufferEvent::LanguageChanged => {
+                Event::LanguageChanged(buffer.read(cx).remote_id())
+            }
+            language::BufferEvent::Reparsed => Event::Reparsed(buffer.read(cx).remote_id()),
+            language::BufferEvent::DiagnosticsUpdated => Event::DiagnosticsUpdated,
+            language::BufferEvent::Closed => Event::Closed,
+            language::BufferEvent::Discarded => Event::Discarded,
+            language::BufferEvent::CapabilityChanged => {
                 self.capability = buffer.read(cx).capability();
                 Event::CapabilityChanged
             }
 
             //
-            language::Event::Operation(_) => return,
+            language::BufferEvent::Operation(_) => return,
         });
     }
 

crates/project/src/buffer_store.rs 🔗

@@ -15,7 +15,7 @@ use gpui::{
 use http_client::Url;
 use language::{
     proto::{deserialize_line_ending, deserialize_version, serialize_version, split_operations},
-    Buffer, Capability, Event as BufferEvent, File as _, Language, Operation,
+    Buffer, BufferEvent, Capability, File as _, Language, Operation,
 };
 use rpc::{proto, AnyProtoClient, ErrorExt as _, TypedEnvelope};
 use smol::channel::Receiver;
@@ -62,7 +62,7 @@ pub enum BufferStoreEvent {
     },
 }
 
-#[derive(Default)]
+#[derive(Default, Debug)]
 pub struct ProjectTransaction(pub HashMap<Model<Buffer>, language::Transaction>);
 
 impl EventEmitter<BufferStoreEvent> for BufferStore {}
@@ -1054,7 +1054,11 @@ impl BufferStore {
                         buffer.update(cx, |buffer, cx| buffer.apply_ops(ops, cx))?;
                     }
                     OpenBuffer::Operations(operations) => operations.extend_from_slice(&ops),
-                    OpenBuffer::Weak(_) => {}
+                    OpenBuffer::Weak(buffer) => {
+                        if let Some(buffer) = buffer.upgrade() {
+                            buffer.update(cx, |buffer, cx| buffer.apply_ops(ops, cx))?;
+                        }
+                    }
                 },
                 hash_map::Entry::Vacant(e) => {
                     e.insert(OpenBuffer::Operations(ops));

crates/project/src/lsp_command.rs 🔗

@@ -46,8 +46,8 @@ pub fn lsp_formatting_options(settings: &LanguageSettings) -> lsp::FormattingOpt
 }
 
 #[async_trait(?Send)]
-pub trait LspCommand: 'static + Sized + Send {
-    type Response: 'static + Default + Send;
+pub trait LspCommand: 'static + Sized + Send + std::fmt::Debug {
+    type Response: 'static + Default + Send + std::fmt::Debug;
     type LspRequest: 'static + Send + lsp::request::Request;
     type ProtoRequest: 'static + Send + proto::RequestMessage;
 
@@ -104,72 +104,80 @@ pub trait LspCommand: 'static + Sized + Send {
     fn buffer_id_from_proto(message: &Self::ProtoRequest) -> Result<BufferId>;
 }
 
+#[derive(Debug)]
 pub(crate) struct PrepareRename {
     pub position: PointUtf16,
 }
 
+#[derive(Debug)]
 pub(crate) struct PerformRename {
     pub position: PointUtf16,
     pub new_name: String,
     pub push_to_history: bool,
 }
 
+#[derive(Debug)]
 pub struct GetDefinition {
     pub position: PointUtf16,
 }
 
+#[derive(Debug)]
 pub(crate) struct GetDeclaration {
     pub position: PointUtf16,
 }
 
+#[derive(Debug)]
 pub(crate) struct GetTypeDefinition {
     pub position: PointUtf16,
 }
 
+#[derive(Debug)]
 pub(crate) struct GetImplementation {
     pub position: PointUtf16,
 }
-
+#[derive(Debug)]
 pub(crate) struct GetReferences {
     pub position: PointUtf16,
 }
 
+#[derive(Debug)]
 pub(crate) struct GetDocumentHighlights {
     pub position: PointUtf16,
 }
 
-#[derive(Clone)]
+#[derive(Clone, Debug)]
 pub(crate) struct GetSignatureHelp {
     pub position: PointUtf16,
 }
 
-#[derive(Clone)]
+#[derive(Clone, Debug)]
 pub(crate) struct GetHover {
     pub position: PointUtf16,
 }
 
+#[derive(Debug)]
 pub(crate) struct GetCompletions {
     pub position: PointUtf16,
     pub context: CompletionContext,
 }
 
-#[derive(Clone)]
+#[derive(Clone, Debug)]
 pub(crate) struct GetCodeActions {
     pub range: Range<Anchor>,
     pub kinds: Option<Vec<lsp::CodeActionKind>>,
 }
-
+#[derive(Debug)]
 pub(crate) struct OnTypeFormatting {
     pub position: PointUtf16,
     pub trigger: String,
     pub options: lsp::FormattingOptions,
     pub push_to_history: bool,
 }
-
+#[derive(Debug)]
 pub(crate) struct InlayHints {
     pub range: Range<Anchor>,
 }
-
+#[derive(Debug)]
 pub(crate) struct LinkedEditingRange {
     pub position: Anchor,
 }

crates/project/src/lsp_ext_command.rs 🔗

@@ -36,7 +36,7 @@ impl ExpandedMacro {
         self.name.is_empty() && self.expansion.is_empty()
     }
 }
-
+#[derive(Debug)]
 pub struct ExpandMacro {
     pub position: PointUtf16,
 }

crates/project/src/lsp_store.rs 🔗

@@ -501,15 +501,15 @@ impl LspStore {
     fn on_buffer_event(
         &mut self,
         buffer: Model<Buffer>,
-        event: &language::Event,
+        event: &language::BufferEvent,
         cx: &mut ModelContext<Self>,
     ) {
         match event {
-            language::Event::Edited { .. } => {
+            language::BufferEvent::Edited { .. } => {
                 self.on_buffer_edited(buffer, cx);
             }
 
-            language::Event::Saved => {
+            language::BufferEvent::Saved => {
                 self.on_buffer_saved(buffer, cx);
             }
 
@@ -2930,6 +2930,7 @@ impl LspStore {
             })
             .map(|(_, server)| server.server_id())
             .collect::<Vec<_>>();
+
         let mut response_results = server_ids
             .into_iter()
             .map(|server_id| {

crates/project/src/project.rs 🔗

@@ -50,9 +50,9 @@ use language::{
         deserialize_anchor, serialize_anchor, serialize_line_ending, serialize_version,
         split_operations,
     },
-    Buffer, CachedLspAdapter, Capability, CodeLabel, ContextProvider, DiagnosticEntry, Diff,
-    Documentation, Event as BufferEvent, File as _, Language, LanguageRegistry, LanguageServerName,
-    PointUtf16, ToOffset, ToPointUtf16, Transaction, Unclipped,
+    Buffer, BufferEvent, CachedLspAdapter, Capability, CodeLabel, ContextProvider, DiagnosticEntry,
+    Diff, Documentation, File as _, Language, LanguageRegistry, LanguageServerName, PointUtf16,
+    ToOffset, ToPointUtf16, Transaction, Unclipped,
 };
 use lsp::{CompletionContext, DocumentHighlightKind, LanguageServer, LanguageServerId};
 use lsp_command::*;
@@ -799,6 +799,7 @@ impl Project {
             client.add_model_message_handler(Self::handle_create_buffer_for_peer);
             client.add_model_message_handler(BufferStore::handle_update_buffer_file);
             client.add_model_message_handler(BufferStore::handle_update_diff_base);
+            client.add_model_request_handler(BufferStore::handle_update_buffer);
             LspStore::init(&client);
             SettingsObserver::init(&client);
 
@@ -1367,7 +1368,13 @@ impl Project {
     pub fn replica_id(&self) -> ReplicaId {
         match self.client_state {
             ProjectClientState::Remote { replica_id, .. } => replica_id,
-            _ => 0,
+            _ => {
+                if self.ssh_session.is_some() {
+                    1
+                } else {
+                    0
+                }
+            }
         }
     }
 
@@ -1818,6 +1825,15 @@ impl Project {
         }
     }
 
+    pub fn is_via_ssh(&self) -> bool {
+        match &self.client_state {
+            ProjectClientState::Local | ProjectClientState::Shared { .. } => {
+                self.ssh_session.is_some()
+            }
+            ProjectClientState::Remote { .. } => false,
+        }
+    }
+
     pub fn is_via_collab(&self) -> bool {
         match &self.client_state {
             ProjectClientState::Local | ProjectClientState::Shared { .. } => false,

crates/project/src/project_tests.rs 🔗

@@ -315,7 +315,7 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
     let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await;
     let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 
-    let mut fake_rust_servers = language_registry.register_fake_lsp_adapter(
+    let mut fake_rust_servers = language_registry.register_fake_lsp(
         "Rust",
         FakeLspAdapter {
             name: "the-rust-language-server",
@@ -335,7 +335,7 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
             ..Default::default()
         },
     );
-    let mut fake_json_servers = language_registry.register_fake_lsp_adapter(
+    let mut fake_json_servers = language_registry.register_fake_lsp(
         "JSON",
         FakeLspAdapter {
             name: "the-json-language-server",
@@ -716,7 +716,7 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
     let project = Project::test(fs.clone(), ["/the-root".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_adapter(
+    let mut fake_servers = language_registry.register_fake_lsp(
         "Rust",
         FakeLspAdapter {
             name: "the-language-server",
@@ -1125,7 +1125,7 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
     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_adapter(
+    let mut fake_servers = language_registry.register_fake_lsp(
         "Rust",
         FakeLspAdapter {
             disk_based_diagnostics_progress_token: Some(progress_token.into()),
@@ -1247,7 +1247,7 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC
 
     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_adapter(
+    let mut fake_servers = language_registry.register_fake_lsp(
         "Rust",
         FakeLspAdapter {
             name: "the-language-server",
@@ -1324,8 +1324,7 @@ async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAp
 
     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_adapter("Rust", FakeLspAdapter::default());
+    let mut fake_servers = language_registry.register_fake_lsp("Rust", FakeLspAdapter::default());
 
     let buffer = project
         .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
@@ -1404,8 +1403,7 @@ async fn test_restarted_server_reporting_invalid_buffer_version(cx: &mut gpui::T
     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_adapter("Rust", FakeLspAdapter::default());
+    let mut fake_servers = language_registry.register_fake_lsp("Rust", FakeLspAdapter::default());
 
     let buffer = project
         .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
@@ -1445,7 +1443,7 @@ async fn test_cancel_language_server_work(cx: &mut gpui::TestAppContext) {
 
     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_adapter(
+    let mut fake_servers = language_registry.register_fake_lsp(
         "Rust",
         FakeLspAdapter {
             name: "the-language-server",
@@ -1506,14 +1504,14 @@ async fn test_toggling_enable_language_server(cx: &mut gpui::TestAppContext) {
     let project = Project::test(fs, ["/dir".as_ref()], cx).await;
     let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 
-    let mut fake_rust_servers = language_registry.register_fake_lsp_adapter(
+    let mut fake_rust_servers = language_registry.register_fake_lsp(
         "Rust",
         FakeLspAdapter {
             name: "rust-lsp",
             ..Default::default()
         },
     );
-    let mut fake_js_servers = language_registry.register_fake_lsp_adapter(
+    let mut fake_js_servers = language_registry.register_fake_lsp(
         "JavaScript",
         FakeLspAdapter {
             name: "js-lsp",
@@ -1627,7 +1625,7 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
     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_adapter(
+    let mut fake_servers = language_registry.register_fake_lsp(
         "Rust",
         FakeLspAdapter {
             disk_based_diagnostics_sources: vec!["disk".into()],
@@ -2049,8 +2047,7 @@ async fn test_edits_from_lsp2_with_past_version(cx: &mut gpui::TestAppContext) {
 
     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_adapter("Rust", FakeLspAdapter::default());
+    let mut fake_servers = language_registry.register_fake_lsp("Rust", FakeLspAdapter::default());
 
     let buffer = project
         .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
@@ -2421,8 +2418,7 @@ async fn test_definition(cx: &mut gpui::TestAppContext) {
 
     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_adapter("Rust", FakeLspAdapter::default());
+    let mut fake_servers = language_registry.register_fake_lsp("Rust", FakeLspAdapter::default());
 
     let buffer = project
         .update(cx, |project, cx| project.open_local_buffer("/dir/b.rs", cx))
@@ -2515,7 +2511,7 @@ async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) {
 
     let language_registry = project.read_with(cx, |project, _| project.languages().clone());
     language_registry.add(typescript_lang());
-    let mut fake_language_servers = language_registry.register_fake_lsp_adapter(
+    let mut fake_language_servers = language_registry.register_fake_lsp(
         "TypeScript",
         FakeLspAdapter {
             capabilities: lsp::ServerCapabilities {
@@ -2607,7 +2603,7 @@ async fn test_completions_with_carriage_returns(cx: &mut gpui::TestAppContext) {
 
     let language_registry = project.read_with(cx, |project, _| project.languages().clone());
     language_registry.add(typescript_lang());
-    let mut fake_language_servers = language_registry.register_fake_lsp_adapter(
+    let mut fake_language_servers = language_registry.register_fake_lsp(
         "TypeScript",
         FakeLspAdapter {
             capabilities: lsp::ServerCapabilities {
@@ -2668,7 +2664,7 @@ async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) {
 
     let language_registry = project.read_with(cx, |project, _| project.languages().clone());
     language_registry.add(typescript_lang());
-    let mut fake_language_servers = language_registry.register_fake_lsp_adapter(
+    let mut fake_language_servers = language_registry.register_fake_lsp(
         "TypeScript",
         FakeLspAdapter {
             capabilities: lsp::ServerCapabilities {
@@ -3310,7 +3306,10 @@ async fn test_buffer_is_dirty(cx: &mut gpui::TestAppContext) {
         assert!(buffer.is_dirty());
         assert_eq!(
             *events.lock(),
-            &[language::Event::Edited, language::Event::DirtyChanged]
+            &[
+                language::BufferEvent::Edited,
+                language::BufferEvent::DirtyChanged
+            ]
         );
         events.lock().clear();
         buffer.did_save(buffer.version(), buffer.file().unwrap().mtime(), cx);
@@ -3319,7 +3318,7 @@ async fn test_buffer_is_dirty(cx: &mut gpui::TestAppContext) {
     // after saving, the buffer is not dirty, and emits a saved event.
     buffer1.update(cx, |buffer, cx| {
         assert!(!buffer.is_dirty());
-        assert_eq!(*events.lock(), &[language::Event::Saved]);
+        assert_eq!(*events.lock(), &[language::BufferEvent::Saved]);
         events.lock().clear();
 
         buffer.edit([(1..1, "B")], None, cx);
@@ -3333,9 +3332,9 @@ async fn test_buffer_is_dirty(cx: &mut gpui::TestAppContext) {
         assert_eq!(
             *events.lock(),
             &[
-                language::Event::Edited,
-                language::Event::DirtyChanged,
-                language::Event::Edited,
+                language::BufferEvent::Edited,
+                language::BufferEvent::DirtyChanged,
+                language::BufferEvent::Edited,
             ],
         );
         events.lock().clear();
@@ -3349,7 +3348,10 @@ async fn test_buffer_is_dirty(cx: &mut gpui::TestAppContext) {
 
     assert_eq!(
         *events.lock(),
-        &[language::Event::Edited, language::Event::DirtyChanged]
+        &[
+            language::BufferEvent::Edited,
+            language::BufferEvent::DirtyChanged
+        ]
     );
 
     // When a file is deleted, the buffer is considered dirty.
@@ -3374,8 +3376,8 @@ async fn test_buffer_is_dirty(cx: &mut gpui::TestAppContext) {
     assert_eq!(
         *events.lock(),
         &[
-            language::Event::DirtyChanged,
-            language::Event::FileHandleChanged
+            language::BufferEvent::DirtyChanged,
+            language::BufferEvent::FileHandleChanged
         ]
     );
 
@@ -3401,7 +3403,7 @@ async fn test_buffer_is_dirty(cx: &mut gpui::TestAppContext) {
         .await
         .unwrap();
     cx.executor().run_until_parked();
-    assert_eq!(*events.lock(), &[language::Event::FileHandleChanged]);
+    assert_eq!(*events.lock(), &[language::BufferEvent::FileHandleChanged]);
     cx.update(|cx| assert!(buffer3.read(cx).is_dirty()));
 }
 
@@ -3809,7 +3811,7 @@ async fn test_rename(cx: &mut gpui::TestAppContext) {
 
     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_adapter(
+    let mut fake_servers = language_registry.register_fake_lsp(
         "Rust",
         FakeLspAdapter {
             capabilities: lsp::ServerCapabilities {
@@ -4696,50 +4698,52 @@ async fn test_multiple_language_server_hovers(cx: &mut gpui::TestAppContext) {
         "ESLintServer",
         "NoHoverCapabilitiesServer",
     ];
-    let mut fake_tsx_language_servers = language_registry.register_fake_lsp_adapter(
-        "tsx",
-        FakeLspAdapter {
-            name: language_server_names[0],
-            capabilities: lsp::ServerCapabilities {
-                hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
-                ..lsp::ServerCapabilities::default()
+    let mut language_servers = [
+        language_registry.register_fake_lsp(
+            "tsx",
+            FakeLspAdapter {
+                name: language_server_names[0],
+                capabilities: lsp::ServerCapabilities {
+                    hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
+                    ..lsp::ServerCapabilities::default()
+                },
+                ..FakeLspAdapter::default()
             },
-            ..FakeLspAdapter::default()
-        },
-    );
-    let _a = language_registry.register_fake_lsp_adapter(
-        "tsx",
-        FakeLspAdapter {
-            name: language_server_names[1],
-            capabilities: lsp::ServerCapabilities {
-                hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
-                ..lsp::ServerCapabilities::default()
+        ),
+        language_registry.register_fake_lsp(
+            "tsx",
+            FakeLspAdapter {
+                name: language_server_names[1],
+                capabilities: lsp::ServerCapabilities {
+                    hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
+                    ..lsp::ServerCapabilities::default()
+                },
+                ..FakeLspAdapter::default()
             },
-            ..FakeLspAdapter::default()
-        },
-    );
-    let _b = language_registry.register_fake_lsp_adapter(
-        "tsx",
-        FakeLspAdapter {
-            name: language_server_names[2],
-            capabilities: lsp::ServerCapabilities {
-                hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
-                ..lsp::ServerCapabilities::default()
+        ),
+        language_registry.register_fake_lsp(
+            "tsx",
+            FakeLspAdapter {
+                name: language_server_names[2],
+                capabilities: lsp::ServerCapabilities {
+                    hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
+                    ..lsp::ServerCapabilities::default()
+                },
+                ..FakeLspAdapter::default()
             },
-            ..FakeLspAdapter::default()
-        },
-    );
-    let _c = language_registry.register_fake_lsp_adapter(
-        "tsx",
-        FakeLspAdapter {
-            name: language_server_names[3],
-            capabilities: lsp::ServerCapabilities {
-                hover_provider: None,
-                ..lsp::ServerCapabilities::default()
+        ),
+        language_registry.register_fake_lsp(
+            "tsx",
+            FakeLspAdapter {
+                name: language_server_names[3],
+                capabilities: lsp::ServerCapabilities {
+                    hover_provider: None,
+                    ..lsp::ServerCapabilities::default()
+                },
+                ..FakeLspAdapter::default()
             },
-            ..FakeLspAdapter::default()
-        },
-    );
+        ),
+    ];
 
     let buffer = project
         .update(cx, |p, cx| p.open_local_buffer("/dir/a.tsx", cx))
@@ -4749,7 +4753,7 @@ async fn test_multiple_language_server_hovers(cx: &mut gpui::TestAppContext) {
 
     let mut servers_with_hover_requests = HashMap::default();
     for i in 0..language_server_names.len() {
-        let new_server = fake_tsx_language_servers.next().await.unwrap_or_else(|| {
+        let new_server = language_servers[i].next().await.unwrap_or_else(|| {
             panic!(
                 "Failed to get language server #{i} with name {}",
                 &language_server_names[i]
@@ -4840,7 +4844,7 @@ async fn test_hovers_with_empty_parts(cx: &mut gpui::TestAppContext) {
 
     let language_registry = project.read_with(cx, |project, _| project.languages().clone());
     language_registry.add(typescript_lang());
-    let mut fake_language_servers = language_registry.register_fake_lsp_adapter(
+    let mut fake_language_servers = language_registry.register_fake_lsp(
         "TypeScript",
         FakeLspAdapter {
             capabilities: lsp::ServerCapabilities {
@@ -4916,50 +4920,53 @@ async fn test_multiple_language_server_actions(cx: &mut gpui::TestAppContext) {
         "ESLintServer",
         "NoActionsCapabilitiesServer",
     ];
-    let mut fake_tsx_language_servers = language_registry.register_fake_lsp_adapter(
-        "tsx",
-        FakeLspAdapter {
-            name: language_server_names[0],
-            capabilities: lsp::ServerCapabilities {
-                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
-                ..lsp::ServerCapabilities::default()
+
+    let mut language_server_rxs = [
+        language_registry.register_fake_lsp(
+            "tsx",
+            FakeLspAdapter {
+                name: language_server_names[0],
+                capabilities: lsp::ServerCapabilities {
+                    code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
+                    ..lsp::ServerCapabilities::default()
+                },
+                ..FakeLspAdapter::default()
             },
-            ..FakeLspAdapter::default()
-        },
-    );
-    let _a = language_registry.register_fake_lsp_adapter(
-        "tsx",
-        FakeLspAdapter {
-            name: language_server_names[1],
-            capabilities: lsp::ServerCapabilities {
-                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
-                ..lsp::ServerCapabilities::default()
+        ),
+        language_registry.register_fake_lsp(
+            "tsx",
+            FakeLspAdapter {
+                name: language_server_names[1],
+                capabilities: lsp::ServerCapabilities {
+                    code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
+                    ..lsp::ServerCapabilities::default()
+                },
+                ..FakeLspAdapter::default()
             },
-            ..FakeLspAdapter::default()
-        },
-    );
-    let _b = language_registry.register_fake_lsp_adapter(
-        "tsx",
-        FakeLspAdapter {
-            name: language_server_names[2],
-            capabilities: lsp::ServerCapabilities {
-                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
-                ..lsp::ServerCapabilities::default()
+        ),
+        language_registry.register_fake_lsp(
+            "tsx",
+            FakeLspAdapter {
+                name: language_server_names[2],
+                capabilities: lsp::ServerCapabilities {
+                    code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
+                    ..lsp::ServerCapabilities::default()
+                },
+                ..FakeLspAdapter::default()
             },
-            ..FakeLspAdapter::default()
-        },
-    );
-    let _c = language_registry.register_fake_lsp_adapter(
-        "tsx",
-        FakeLspAdapter {
-            name: language_server_names[3],
-            capabilities: lsp::ServerCapabilities {
-                code_action_provider: None,
-                ..lsp::ServerCapabilities::default()
+        ),
+        language_registry.register_fake_lsp(
+            "tsx",
+            FakeLspAdapter {
+                name: language_server_names[3],
+                capabilities: lsp::ServerCapabilities {
+                    code_action_provider: None,
+                    ..lsp::ServerCapabilities::default()
+                },
+                ..FakeLspAdapter::default()
             },
-            ..FakeLspAdapter::default()
-        },
-    );
+        ),
+    ];
 
     let buffer = project
         .update(cx, |p, cx| p.open_local_buffer("/dir/a.tsx", cx))
@@ -4969,13 +4976,14 @@ async fn test_multiple_language_server_actions(cx: &mut gpui::TestAppContext) {
 
     let mut servers_with_actions_requests = HashMap::default();
     for i in 0..language_server_names.len() {
-        let new_server = fake_tsx_language_servers.next().await.unwrap_or_else(|| {
+        let new_server = language_server_rxs[i].next().await.unwrap_or_else(|| {
             panic!(
                 "Failed to get language server #{i} with name {}",
                 &language_server_names[i]
             )
         });
         let new_server_name = new_server.server.name();
+
         assert!(
             !servers_with_actions_requests.contains_key(new_server_name),
             "Unexpected: initialized server with the same name twice. Name: `{new_server_name}`"
@@ -5023,6 +5031,8 @@ async fn test_multiple_language_server_actions(cx: &mut gpui::TestAppContext) {
     let code_actions_task = project.update(cx, |project, cx| {
         project.code_actions(&buffer, 0..buffer.read(cx).len(), cx)
     });
+
+    // cx.run_until_parked();
     let _: Vec<()> = futures::future::join_all(servers_with_actions_requests.into_values().map(
         |mut code_actions_request| async move {
             code_actions_request

crates/project_symbols/src/project_symbols.rs 🔗

@@ -288,7 +288,7 @@ mod tests {
             None,
         )));
         let mut fake_servers =
-            language_registry.register_fake_lsp_adapter("Rust", FakeLspAdapter::default());
+            language_registry.register_fake_lsp("Rust", FakeLspAdapter::default());
 
         let _buffer = project
             .update(cx, |project, cx| {

crates/proto/proto/zed.proto 🔗

@@ -497,6 +497,7 @@ message ShareProject {
     uint64 room_id = 1;
     repeated WorktreeMetadata worktrees = 2;
     optional uint64 dev_server_project_id = 3;
+    bool is_ssh_project = 4;
 }
 
 message ShareProjectResponse {

crates/remote_server/Cargo.toml 🔗

@@ -46,7 +46,9 @@ gpui = { workspace = true, features = ["test-support"] }
 http_client = { workspace = true, features = ["test-support"] }
 language = { workspace = true, features = ["test-support"] }
 node_runtime = { workspace = true, features = ["test-support"] }
+project = { workspace = true, features = ["test-support"] }
 remote = { workspace = true, features = ["test-support"] }
+lsp = { workspace = true, features=["test-support"] }
 
 serde_json.workspace = true
 

crates/remote_server/src/headless_project.rs 🔗

@@ -1,10 +1,13 @@
 use anyhow::{anyhow, Result};
 use fs::Fs;
 use gpui::{AppContext, AsyncAppContext, Context, Model, ModelContext};
-use language::LanguageRegistry;
+use language::{proto::serialize_operation, Buffer, BufferEvent, LanguageRegistry};
 use project::{
-    buffer_store::BufferStore, project_settings::SettingsObserver, search::SearchQuery,
-    worktree_store::WorktreeStore, LspStore, ProjectPath, WorktreeId,
+    buffer_store::{BufferStore, BufferStoreEvent},
+    project_settings::SettingsObserver,
+    search::SearchQuery,
+    worktree_store::WorktreeStore,
+    LspStore, ProjectPath, WorktreeId,
 };
 use remote::SshSession;
 use rpc::{
@@ -26,6 +29,7 @@ pub struct HeadlessProject {
     pub lsp_store: Model<LspStore>,
     pub settings_observer: Model<SettingsObserver>,
     pub next_entry_id: Arc<AtomicUsize>,
+    pub languages: Arc<LanguageRegistry>,
 }
 
 impl HeadlessProject {
@@ -60,7 +64,7 @@ impl HeadlessProject {
                 buffer_store.clone(),
                 worktree_store.clone(),
                 environment,
-                languages,
+                languages.clone(),
                 None,
                 fs.clone(),
                 cx,
@@ -69,6 +73,17 @@ impl HeadlessProject {
             lsp_store
         });
 
+        cx.subscribe(
+            &buffer_store,
+            |_this, _buffer_store, event, cx| match event {
+                BufferStoreEvent::BufferAdded(buffer) => {
+                    cx.subscribe(buffer, Self::on_buffer_event).detach();
+                }
+                _ => {}
+            },
+        )
+        .detach();
+
         let client: AnyProtoClient = session.clone().into();
 
         session.subscribe_to_entity(SSH_PROJECT_ID, &worktree_store);
@@ -103,6 +118,26 @@ impl HeadlessProject {
             buffer_store,
             lsp_store,
             next_entry_id: Default::default(),
+            languages,
+        }
+    }
+
+    fn on_buffer_event(
+        &mut self,
+        buffer: Model<Buffer>,
+        event: &BufferEvent,
+        cx: &mut ModelContext<Self>,
+    ) {
+        match event {
+            BufferEvent::Operation(op) => cx
+                .background_executor()
+                .spawn(self.session.request(proto::UpdateBuffer {
+                    project_id: SSH_PROJECT_ID,
+                    buffer_id: buffer.read(cx).remote_id().to_proto(),
+                    operations: vec![serialize_operation(op)],
+                }))
+                .detach(),
+            _ => {}
         }
     }
 

crates/remote_server/src/remote_editing_tests.rs 🔗

@@ -6,8 +6,9 @@ use gpui::{Context, Model, TestAppContext};
 use http_client::FakeHttpClient;
 use language::{
     language_settings::{all_language_settings, AllLanguageSettings},
-    Buffer, FakeLspAdapter, LanguageConfig, LanguageMatcher, LanguageRegistry,
+    Buffer, FakeLspAdapter, LanguageConfig, LanguageMatcher, LanguageRegistry, LanguageServerName,
 };
+use lsp::{CompletionContext, CompletionResponse, CompletionTriggerKind};
 use node_runtime::FakeNodeRuntime;
 use project::{
     search::{SearchQuery, SearchResult},
@@ -317,6 +318,15 @@ async fn test_remote_lsp(cx: &mut TestAppContext, server_cx: &mut TestAppContext
             },
         )
     });
+
+    let mut fake_lsp = server_cx.update(|cx| {
+        headless.read(cx).languages.register_fake_language_server(
+            LanguageServerName("rust-analyzer".into()),
+            Default::default(),
+            None,
+        )
+    });
+
     cx.run_until_parked();
 
     let worktree_id = project
@@ -339,6 +349,8 @@ async fn test_remote_lsp(cx: &mut TestAppContext, server_cx: &mut TestAppContext
         .unwrap();
     cx.run_until_parked();
 
+    let fake_lsp = fake_lsp.next().await.unwrap();
+
     cx.read(|cx| {
         let file = buffer.read(cx).file();
         assert_eq!(
@@ -370,6 +382,62 @@ async fn test_remote_lsp(cx: &mut TestAppContext, server_cx: &mut TestAppContext
         let lsp_store = headless.read(cx).lsp_store.read(cx);
         assert_eq!(lsp_store.as_local().unwrap().language_servers.len(), 1);
     });
+
+    fake_lsp.handle_request::<lsp::request::Completion, _, _>(|_, _| async move {
+        Ok(Some(CompletionResponse::Array(vec![lsp::CompletionItem {
+            label: "boop".to_string(),
+            ..Default::default()
+        }])))
+    });
+
+    let result = project
+        .update(cx, |project, cx| {
+            project.completions(
+                &buffer,
+                0,
+                CompletionContext {
+                    trigger_kind: CompletionTriggerKind::INVOKED,
+                    trigger_character: None,
+                },
+                cx,
+            )
+        })
+        .await
+        .unwrap();
+
+    assert_eq!(
+        result.into_iter().map(|c| c.label.text).collect::<Vec<_>>(),
+        vec!["boop".to_string()]
+    );
+
+    fake_lsp.handle_request::<lsp::request::Rename, _, _>(|_, _| async move {
+        Ok(Some(lsp::WorkspaceEdit {
+            changes: Some(
+                [(
+                    lsp::Url::from_file_path("/code/project1/src/lib.rs").unwrap(),
+                    vec![lsp::TextEdit::new(
+                        lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 6)),
+                        "two".to_string(),
+                    )],
+                )]
+                .into_iter()
+                .collect(),
+            ),
+            ..Default::default()
+        }))
+    });
+
+    project
+        .update(cx, |project, cx| {
+            project.perform_rename(buffer.clone(), 3, "two".to_string(), true, cx)
+        })
+        .await
+        .unwrap();
+
+    cx.run_until_parked();
+    buffer.update(cx, |buffer, _| {
+        assert_eq!(buffer.text(), "fn two() -> usize { 1 }")
+    })
 }
 
 fn init_logger() {