From e1e8c1786e6788735d7063ad7b942b253107e9fb Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 31 Mar 2025 19:13:09 +0300 Subject: [PATCH] Fix remote clients unable to query custom, lsp_ext, commands (#27775) Closes https://github.com/zed-industries/zed/issues/20583 Closes https://github.com/zed-industries/zed/issues/27133 A preparation for rust-analyzer's LSP tasks fetching, ensures all remote clients are able to query custom, lsp_ext, commands. Release Notes: - Fixed remote clients unable to query custom, lsp_ext, commands --- crates/collab/src/rpc.rs | 8 + crates/collab/src/tests/editor_tests.rs | 154 ++++++++++++++++- crates/editor/src/clangd_ext.rs | 77 +++++---- crates/editor/src/element.rs | 1 + crates/editor/src/lsp_ext.rs | 65 +++---- crates/editor/src/rust_analyzer_ext.rs | 163 ++++++++++++------ crates/editor/src/test.rs | 2 + crates/project/src/lsp_store.rs | 36 +++- .../src/lsp_store/rust_analyzer_ext.rs | 8 - crates/project/src/project.rs | 37 ++-- crates/proto/proto/zed.proto | 15 +- crates/proto/src/proto.rs | 4 + 12 files changed, 430 insertions(+), 140 deletions(-) diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index d0f99dc50b..393ab5ec3e 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -316,6 +316,14 @@ impl Server { .add_request_handler(forward_read_only_project_request::) .add_request_handler(forward_read_only_project_request::) .add_request_handler(forward_read_only_project_request::) + .add_request_handler(forward_read_only_project_request::) + .add_request_handler(forward_read_only_project_request::) + .add_request_handler( + forward_read_only_project_request::, + ) + .add_request_handler( + forward_read_only_project_request::, + ) .add_request_handler( forward_mutating_project_request::, ) diff --git a/crates/collab/src/tests/editor_tests.rs b/crates/collab/src/tests/editor_tests.rs index 6e52e5df84..8a595cc2e1 100644 --- a/crates/collab/src/tests/editor_tests.rs +++ b/crates/collab/src/tests/editor_tests.rs @@ -5,10 +5,13 @@ use crate::{ use call::ActiveCall; use editor::{ actions::{ - ConfirmCodeAction, ConfirmCompletion, ConfirmRename, ContextMenuFirst, Redo, Rename, - ToggleCodeActions, Undo, + ConfirmCodeAction, ConfirmCompletion, ConfirmRename, ContextMenuFirst, + ExpandMacroRecursively, Redo, Rename, ToggleCodeActions, Undo, + }, + test::{ + editor_test_context::{AssertionContextManager, EditorTestContext}, + expand_macro_recursively, }, - test::editor_test_context::{AssertionContextManager, EditorTestContext}, Editor, RowInfo, }; use fs::Fs; @@ -20,6 +23,10 @@ use language::{ FakeLspAdapter, }; use project::{ + lsp_store::{ + lsp_ext_command::{ExpandedMacro, LspExpandMacro}, + rust_analyzer_ext::RUST_ANALYZER_NAME, + }, project_settings::{InlineBlameSettings, ProjectSettings}, ProjectPath, SERVER_PROGRESS_THROTTLE_TIMEOUT, }; @@ -2619,6 +2626,147 @@ async fn test_add_breakpoints(cx_a: &mut TestAppContext, cx_b: &mut TestAppConte assert_eq!(breakpoints_a, breakpoints_b); } +#[gpui::test] +async fn test_client_can_query_lsp_ext(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { + let mut server = TestServer::start(cx_a.executor()).await; + let client_a = server.create_client(cx_a, "user_a").await; + let client_b = server.create_client(cx_b, "user_b").await; + server + .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)]) + .await; + let active_call_a = cx_a.read(ActiveCall::global); + let active_call_b = cx_b.read(ActiveCall::global); + + cx_a.update(editor::init); + cx_b.update(editor::init); + + 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( + "Rust", + FakeLspAdapter { + name: RUST_ANALYZER_NAME, + ..FakeLspAdapter::default() + }, + ); + + client_a + .fs() + .insert_tree( + path!("/a"), + json!({ + "main.rs": "fn main() {}", + }), + ) + .await; + let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await; + active_call_a + .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx)) + .await + .unwrap(); + let project_id = active_call_a + .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) + .await + .unwrap(); + + let project_b = client_b.join_remote_project(project_id, cx_b).await; + active_call_b + .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx)) + .await + .unwrap(); + + let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a); + let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); + + let editor_a = workspace_a + .update_in(cx_a, |workspace, window, cx| { + workspace.open_path((worktree_id, "main.rs"), None, true, window, cx) + }) + .await + .unwrap() + .downcast::() + .unwrap(); + + let editor_b = workspace_b + .update_in(cx_b, |workspace, window, cx| { + workspace.open_path((worktree_id, "main.rs"), None, true, window, cx) + }) + .await + .unwrap() + .downcast::() + .unwrap(); + + let fake_language_server = fake_language_servers.next().await.unwrap(); + + // host + let mut expand_request_a = + fake_language_server.set_request_handler::(|params, _| async move { + assert_eq!( + params.text_document.uri, + lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(), + ); + assert_eq!(params.position, lsp::Position::new(0, 0),); + Ok(Some(ExpandedMacro { + name: "test_macro_name".to_string(), + expansion: "test_macro_expansion on the host".to_string(), + })) + }); + + editor_a.update_in(cx_a, |editor, window, cx| { + expand_macro_recursively(editor, &ExpandMacroRecursively, window, cx) + }); + expand_request_a.next().await.unwrap(); + cx_a.run_until_parked(); + + workspace_a.update(cx_a, |workspace, cx| { + workspace.active_pane().update(cx, |pane, cx| { + assert_eq!( + pane.items_len(), + 2, + "Should have added a macro expansion to the host's pane" + ); + let new_editor = pane.active_item().unwrap().downcast::().unwrap(); + new_editor.update(cx, |editor, cx| { + assert_eq!(editor.text(cx), "test_macro_expansion on the host"); + }); + }) + }); + + // client + let mut expand_request_b = + fake_language_server.set_request_handler::(|params, _| async move { + assert_eq!( + params.text_document.uri, + lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(), + ); + assert_eq!(params.position, lsp::Position::new(0, 0),); + Ok(Some(ExpandedMacro { + name: "test_macro_name".to_string(), + expansion: "test_macro_expansion on the client".to_string(), + })) + }); + + editor_b.update_in(cx_b, |editor, window, cx| { + expand_macro_recursively(editor, &ExpandMacroRecursively, window, cx) + }); + expand_request_b.next().await.unwrap(); + cx_b.run_until_parked(); + + workspace_b.update(cx_b, |workspace, cx| { + workspace.active_pane().update(cx, |pane, cx| { + assert_eq!( + pane.items_len(), + 2, + "Should have added a macro expansion to the client's pane" + ); + let new_editor = pane.active_item().unwrap().downcast::().unwrap(); + new_editor.update(cx, |editor, cx| { + assert_eq!(editor.text(cx), "test_macro_expansion on the client"); + }); + }) + }); +} + #[track_caller] fn tab_undo_assert( cx_a: &mut EditorTestContext, diff --git a/crates/editor/src/clangd_ext.rs b/crates/editor/src/clangd_ext.rs index 2e4b539b16..aee465a008 100644 --- a/crates/editor/src/clangd_ext.rs +++ b/crates/editor/src/clangd_ext.rs @@ -1,6 +1,8 @@ use anyhow::Context as _; use gpui::{App, Context, Entity, Window}; use language::Language; +use project::lsp_store::lsp_ext_command::SwitchSourceHeaderResult; +use rpc::proto; use url::Url; use workspace::{OpenOptions, OpenVisible}; @@ -27,34 +29,42 @@ pub fn switch_source_header( return; }; - let Some((_, _, server_to_query, buffer)) = - find_specific_language_server_in_selection(editor, cx, is_c_language, CLANGD_SERVER_NAME) - else { - return; - }; - + let server_lookup = + find_specific_language_server_in_selection(editor, cx, is_c_language, CLANGD_SERVER_NAME); let project = project.clone(); - let buffer_snapshot = buffer.read(cx).snapshot(); - let source_file = buffer_snapshot - .file() - .unwrap() - .file_name(cx) - .to_str() - .unwrap() - .to_owned(); - - let switch_source_header_task = project.update(cx, |project, cx| { - project.request_lsp( - buffer, - project::LanguageServerToQuery::Other(server_to_query), - project::lsp_store::lsp_ext_command::SwitchSourceHeader, - cx, - ) - }); + let upstream_client = project.read(cx).lsp_store().read(cx).upstream_client(); cx.spawn_in(window, async move |_editor, cx| { - let switch_source_header = switch_source_header_task - .await - .with_context(|| format!("Switch source/header LSP request for path \"{source_file}\" failed"))?; + let Some((_, _, server_to_query, buffer)) = + server_lookup.await + else { + return Ok(()); + }; + let source_file = buffer.update(cx, |buffer, _| { + buffer.file().map(|file| file.path()).map(|path| path.to_string_lossy().to_string()).unwrap_or_else(|| "Unknown".to_string()) + })?; + + let switch_source_header = if let Some((client, project_id)) = upstream_client { + let buffer_id = buffer.update(cx, |buffer, _| buffer.remote_id())?; + let request = proto::LspExtSwitchSourceHeader { + project_id, + buffer_id: buffer_id.to_proto(), + }; + let response = client + .request(request) + .await + .context("lsp ext switch source header proto request")?; + SwitchSourceHeaderResult(response.target_file) + } else { + project.update(cx, |project, cx| { + project.request_lsp( + buffer, + project::LanguageServerToQuery::Other(server_to_query), + project::lsp_store::lsp_ext_command::SwitchSourceHeader, + cx, + ) + })?.await.with_context(|| format!("Switch source/header LSP request for path \"{source_file}\" failed"))? + }; + if switch_source_header.0.is_empty() { log::info!("Clangd returned an empty string when requesting to switch source/header from \"{source_file}\"" ); return Ok(()); @@ -87,10 +97,15 @@ pub fn switch_source_header( } pub fn apply_related_actions(editor: &Entity, window: &mut Window, cx: &mut App) { - if editor.update(cx, |e, cx| { - find_specific_language_server_in_selection(e, cx, is_c_language, CLANGD_SERVER_NAME) - .is_some() - }) { - register_action(editor, window, switch_source_header); + if editor + .read(cx) + .buffer() + .read(cx) + .all_buffers() + .into_iter() + .filter_map(|buffer| buffer.read(cx).language()) + .any(|language| is_c_language(language)) + { + register_action(&editor, window, switch_source_header); } } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index b5c07e2bc3..63bb66a839 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -187,6 +187,7 @@ impl EditorElement { crate::rust_analyzer_ext::apply_related_actions(editor, window, cx); crate::clangd_ext::apply_related_actions(editor, window, cx); + register_action(editor, window, Editor::open_context_menu); register_action(editor, window, Editor::move_left); register_action(editor, window, Editor::move_right); diff --git a/crates/editor/src/lsp_ext.rs b/crates/editor/src/lsp_ext.rs index e0edd267ac..caa1844d01 100644 --- a/crates/editor/src/lsp_ext.rs +++ b/crates/editor/src/lsp_ext.rs @@ -1,9 +1,8 @@ -use std::collections::hash_map::Entry; use std::sync::Arc; use crate::Editor; -use collections::HashMap; -use gpui::{App, Entity}; +use gpui::{App, AppContext as _, Entity, Task}; +use itertools::Itertools; use language::Buffer; use language::Language; use lsp::LanguageServerId; @@ -14,44 +13,50 @@ pub(crate) fn find_specific_language_server_in_selection( cx: &mut App, filter_language: F, language_server_name: &str, -) -> Option<(Anchor, Arc, LanguageServerId, Entity)> +) -> Task, LanguageServerId, Entity)>> where F: Fn(&Language) -> bool, { let Some(project) = &editor.project else { - return None; + return Task::ready(None); }; - let mut language_servers_for = HashMap::default(); - editor + + let applicable_buffers = editor .selections .disjoint_anchors() .iter() .filter(|selection| selection.start == selection.end) - .filter_map(|selection| Some((selection.start.buffer_id?, selection.start))) - .find_map(|(buffer_id, trigger_anchor)| { + .filter_map(|selection| Some((selection.start, selection.start.buffer_id?))) + .filter_map(|(trigger_anchor, buffer_id)| { let buffer = editor.buffer().read(cx).buffer(buffer_id)?; - let server_id = *match language_servers_for.entry(buffer_id) { - Entry::Occupied(occupied_entry) => occupied_entry.into_mut(), - Entry::Vacant(vacant_entry) => { - let language_server_id = buffer.update(cx, |buffer, cx| { - project.update(cx, |project, cx| { - project.language_server_id_for_name(buffer, language_server_name, cx) - }) - }); - vacant_entry.insert(language_server_id) - } - } - .as_ref()?; - let language = buffer.read(cx).language_at(trigger_anchor.text_anchor)?; - if !filter_language(&language) { - return None; + if filter_language(&language) { + Some((trigger_anchor, buffer, language)) + } else { + None } - Some(( - trigger_anchor, - Arc::clone(&language), - server_id, - buffer.clone(), - )) }) + .unique_by(|(_, buffer, _)| buffer.read(cx).remote_id()) + .collect::>(); + + let applicable_buffer_tasks = applicable_buffers + .into_iter() + .map(|(trigger_anchor, buffer, language)| { + let task = buffer.update(cx, |buffer, cx| { + project.update(cx, |project, cx| { + project.language_server_id_for_name(buffer, language_server_name, cx) + }) + }); + (trigger_anchor, buffer, language, task) + }) + .collect::>(); + cx.background_spawn(async move { + for (trigger_anchor, buffer, language, task) in applicable_buffer_tasks { + if let Some(server_id) = task.await { + return Some((trigger_anchor, language, server_id, buffer)); + } + } + + None + }) } diff --git a/crates/editor/src/rust_analyzer_ext.rs b/crates/editor/src/rust_analyzer_ext.rs index c577dcc2a8..86483051cf 100644 --- a/crates/editor/src/rust_analyzer_ext.rs +++ b/crates/editor/src/rust_analyzer_ext.rs @@ -2,9 +2,13 @@ use std::{fs, path::Path}; use anyhow::Context as _; use gpui::{App, AppContext as _, Context, Entity, Window}; -use language::{Capability, Language}; +use language::{proto::serialize_anchor, Capability, Language}; use multi_buffer::MultiBuffer; -use project::lsp_store::{lsp_ext_command::ExpandMacro, rust_analyzer_ext::RUST_ANALYZER_NAME}; +use project::lsp_store::{ + lsp_ext_command::{DocsUrls, ExpandMacro, ExpandedMacro}, + rust_analyzer_ext::RUST_ANALYZER_NAME, +}; +use rpc::proto; use text::ToPointUtf16; use crate::{ @@ -18,13 +22,16 @@ fn is_rust_language(language: &Language) -> bool { pub fn apply_related_actions(editor: &Entity, window: &mut Window, cx: &mut App) { if editor - .update(cx, |e, cx| { - find_specific_language_server_in_selection(e, cx, is_rust_language, RUST_ANALYZER_NAME) - }) - .is_some() + .read(cx) + .buffer() + .read(cx) + .all_buffers() + .into_iter() + .filter_map(|buffer| buffer.read(cx).language()) + .any(|language| is_rust_language(language)) { - register_action(editor, window, expand_macro_recursively); - register_action(editor, window, open_docs); + register_action(&editor, window, expand_macro_recursively); + register_action(&editor, window, open_docs); } } @@ -44,32 +51,57 @@ pub fn expand_macro_recursively( return; }; - let Some((trigger_anchor, rust_language, server_to_query, buffer)) = - find_specific_language_server_in_selection( - editor, - cx, - is_rust_language, - RUST_ANALYZER_NAME, - ) - else { - return; - }; + let server_lookup = find_specific_language_server_in_selection( + editor, + cx, + is_rust_language, + RUST_ANALYZER_NAME, + ); let project = project.clone(); - let buffer_snapshot = buffer.read(cx).snapshot(); - let position = trigger_anchor.text_anchor.to_point_utf16(&buffer_snapshot); - let expand_macro_task = project.update(cx, |project, cx| { - project.request_lsp( - buffer, - project::LanguageServerToQuery::Other(server_to_query), - ExpandMacro { position }, - cx, - ) - }); + let upstream_client = project.read(cx).lsp_store().read(cx).upstream_client(); cx.spawn_in(window, async move |_editor, cx| { - let macro_expansion = expand_macro_task.await.context("expand macro")?; + let Some((trigger_anchor, rust_language, server_to_query, buffer)) = server_lookup.await + else { + return Ok(()); + }; + + let macro_expansion = if let Some((client, project_id)) = upstream_client { + let buffer_id = buffer.update(cx, |buffer, _| buffer.remote_id())?; + let request = proto::LspExtExpandMacro { + project_id, + buffer_id: buffer_id.to_proto(), + position: Some(serialize_anchor(&trigger_anchor.text_anchor)), + }; + let response = client + .request(request) + .await + .context("lsp ext expand macro proto request")?; + ExpandedMacro { + name: response.name, + expansion: response.expansion, + } + } else { + let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot())?; + let position = trigger_anchor.text_anchor.to_point_utf16(&buffer_snapshot); + project + .update(cx, |project, cx| { + project.request_lsp( + buffer, + project::LanguageServerToQuery::Other(server_to_query), + ExpandMacro { position }, + cx, + ) + })? + .await + .context("expand macro")? + }; + if macro_expansion.is_empty() { - log::info!("Empty macro expansion for position {position:?}"); + log::info!( + "Empty macro expansion for position {:?}", + trigger_anchor.text_anchor + ); return Ok(()); } @@ -111,36 +143,57 @@ pub fn open_docs(editor: &mut Editor, _: &OpenDocs, window: &mut Window, cx: &mu return; }; - let Some((trigger_anchor, _rust_language, server_to_query, buffer)) = - find_specific_language_server_in_selection( - editor, - cx, - is_rust_language, - RUST_ANALYZER_NAME, - ) - else { - return; - }; + let server_lookup = find_specific_language_server_in_selection( + editor, + cx, + is_rust_language, + RUST_ANALYZER_NAME, + ); let project = project.clone(); - let buffer_snapshot = buffer.read(cx).snapshot(); - let position = trigger_anchor.text_anchor.to_point_utf16(&buffer_snapshot); - let open_docs_task = project.update(cx, |project, cx| { - project.request_lsp( - buffer, - project::LanguageServerToQuery::Other(server_to_query), - project::lsp_store::lsp_ext_command::OpenDocs { position }, - cx, - ) - }); - + let upstream_client = project.read(cx).lsp_store().read(cx).upstream_client(); cx.spawn_in(window, async move |_editor, cx| { - let docs_urls = open_docs_task.await.context("open docs")?; - if docs_urls.is_empty() { - log::debug!("Empty docs urls for position {position:?}"); + let Some((trigger_anchor, _, server_to_query, buffer)) = server_lookup.await else { return Ok(()); + }; + + let docs_urls = if let Some((client, project_id)) = upstream_client { + let buffer_id = buffer.update(cx, |buffer, _| buffer.remote_id())?; + let request = proto::LspExtOpenDocs { + project_id, + buffer_id: buffer_id.to_proto(), + position: Some(serialize_anchor(&trigger_anchor.text_anchor)), + }; + let response = client + .request(request) + .await + .context("lsp ext open docs proto request")?; + DocsUrls { + web: response.web, + local: response.local, + } } else { - log::debug!("{:?}", docs_urls); + let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot())?; + let position = trigger_anchor.text_anchor.to_point_utf16(&buffer_snapshot); + project + .update(cx, |project, cx| { + project.request_lsp( + buffer, + project::LanguageServerToQuery::Other(server_to_query), + project::lsp_store::lsp_ext_command::OpenDocs { position }, + cx, + ) + })? + .await + .context("open docs")? + }; + + if docs_urls.is_empty() { + log::debug!( + "Empty docs urls for position {:?}", + trigger_anchor.text_anchor + ); + return Ok(()); } workspace.update(cx, |_workspace, cx| { diff --git a/crates/editor/src/test.rs b/crates/editor/src/test.rs index b494cce8ed..f853aba877 100644 --- a/crates/editor/src/test.rs +++ b/crates/editor/src/test.rs @@ -14,6 +14,8 @@ use gpui::{ use project::Project; use util::test::{marked_text_offsets, marked_text_ranges}; +pub use crate::rust_analyzer_ext::expand_macro_recursively; + #[cfg(test)] #[ctor::ctor] fn init_logger() { diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index d598fe4ecf..99fc0ce6dc 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -3425,6 +3425,7 @@ impl LspStore { client.add_entity_request_handler(Self::handle_apply_additional_edits_for_completion); client.add_entity_request_handler(Self::handle_register_buffer_with_language_servers); client.add_entity_request_handler(Self::handle_rename_project_entry); + client.add_entity_request_handler(Self::handle_language_server_id_for_name); client.add_entity_request_handler(Self::handle_lsp_command::); client.add_entity_request_handler(Self::handle_lsp_command::); client.add_entity_request_handler(Self::handle_lsp_command::); @@ -3436,8 +3437,13 @@ impl LspStore { client.add_entity_request_handler(Self::handle_lsp_command::); client.add_entity_request_handler(Self::handle_lsp_command::); client.add_entity_request_handler(Self::handle_lsp_command::); - client.add_entity_request_handler(Self::handle_lsp_command::); client.add_entity_request_handler(Self::handle_lsp_command::); + + client.add_entity_request_handler(Self::handle_lsp_command::); + client.add_entity_request_handler(Self::handle_lsp_command::); + client.add_entity_request_handler( + Self::handle_lsp_command::, + ); } pub fn as_remote(&self) -> Option<&RemoteLspStore> { @@ -6986,6 +6992,34 @@ impl LspStore { Ok(proto::Ack {}) } + async fn handle_language_server_id_for_name( + lsp_store: Entity, + envelope: TypedEnvelope, + mut cx: AsyncApp, + ) -> Result { + let buffer_id = BufferId::new(envelope.payload.buffer_id)?; + let name = &envelope.payload.name; + lsp_store + .update(&mut cx, |lsp_store, cx| { + let buffer = lsp_store.buffer_store.read(cx).get_existing(buffer_id)?; + let server_id = buffer.update(cx, |buffer, cx| { + lsp_store + .language_servers_for_local_buffer(buffer, cx) + .find_map(|(adapter, server)| { + if adapter.name.0.as_ref() == name { + Some(server.server_id()) + } else { + None + } + }) + }); + Ok(server_id) + })? + .map(|server_id| proto::LanguageServerIdForNameResponse { + server_id: server_id.map(|id| id.to_proto()), + }) + } + async fn handle_rename_project_entry( this: Entity, envelope: TypedEnvelope, diff --git a/crates/project/src/lsp_store/rust_analyzer_ext.rs b/crates/project/src/lsp_store/rust_analyzer_ext.rs index 191c6b5a91..7f9abe1a3f 100644 --- a/crates/project/src/lsp_store/rust_analyzer_ext.rs +++ b/crates/project/src/lsp_store/rust_analyzer_ext.rs @@ -6,14 +6,6 @@ use crate::{LanguageServerPromptRequest, LspStore, LspStoreEvent}; pub const RUST_ANALYZER_NAME: &str = "rust-analyzer"; -pub const EXTRA_SUPPORTED_COMMANDS: &[&str] = &[ - "rust-analyzer.runSingle", - "rust-analyzer.showReferences", - "rust-analyzer.gotoLocation", - "rust-analyzer.triggerParameterHints", - "rust-analyzer.rename", -]; - /// Experimental: Informs the end user about the state of the server /// /// [Rust Analyzer Specification](https://github.com/rust-lang/rust-analyzer/blob/master/docs/dev/lsp-extensions.md#server-status) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 5284e8ebb5..5a71a87f13 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -4736,17 +4736,32 @@ impl Project { buffer: &Buffer, name: &str, cx: &mut App, - ) -> Option { - self.lsp_store.update(cx, |this, cx| { - this.language_servers_for_local_buffer(buffer, cx) - .find_map(|(adapter, server)| { - if adapter.name.0 == name { - Some(server.server_id()) - } else { - None - } - }) - }) + ) -> Task> { + if self.is_local() { + Task::ready(self.lsp_store.update(cx, |lsp_store, cx| { + lsp_store + .language_servers_for_local_buffer(buffer, cx) + .find_map(|(adapter, server)| { + if adapter.name.0 == name { + Some(server.server_id()) + } else { + None + } + }) + })) + } else if let Some(project_id) = self.remote_id() { + let request = self.client.request(proto::LanguageServerIdForName { + project_id, + buffer_id: buffer.remote_id().to_proto(), + name: name.to_string(), + }); + cx.background_spawn(async move { + let response = request.await.log_err()?; + response.server_id.map(LanguageServerId::from_proto) + }) + } else { + Task::ready(None) + } } pub fn has_language_servers_for(&self, buffer: &Buffer, cx: &mut App) -> bool { diff --git a/crates/proto/proto/zed.proto b/crates/proto/proto/zed.proto index 1bfc9337ce..e14da68aa7 100644 --- a/crates/proto/proto/zed.proto +++ b/crates/proto/proto/zed.proto @@ -361,7 +361,10 @@ message Envelope { RemoveRepository remove_repository = 329; GetDocumentSymbols get_document_symbols = 330; - GetDocumentSymbolsResponse get_document_symbols_response = 331; // current max + GetDocumentSymbolsResponse get_document_symbols_response = 331; + + LanguageServerIdForName language_server_id_for_name = 332; + LanguageServerIdForNameResponse language_server_id_for_name_response = 333; // current max } reserved 87 to 88; @@ -3567,3 +3570,13 @@ message GitInit { string abs_path = 2; string fallback_branch_name = 3; } + +message LanguageServerIdForName { + uint64 project_id = 1; + uint64 buffer_id = 2; + string name = 3; +} + +message LanguageServerIdForNameResponse { + optional uint64 server_id = 1; +} diff --git a/crates/proto/src/proto.rs b/crates/proto/src/proto.rs index 4720246c00..3bebe4da2a 100644 --- a/crates/proto/src/proto.rs +++ b/crates/proto/src/proto.rs @@ -302,6 +302,8 @@ messages!( (GetImplementationResponse, Background), (GetLlmToken, Background), (GetLlmTokenResponse, Background), + (LanguageServerIdForName, Background), + (LanguageServerIdForNameResponse, Background), (OpenUnstagedDiff, Foreground), (OpenUnstagedDiffResponse, Foreground), (OpenUncommittedDiff, Foreground), @@ -580,6 +582,7 @@ request_messages!( (UpdateWorktree, Ack), (UpdateRepository, Ack), (RemoveRepository, Ack), + (LanguageServerIdForName, LanguageServerIdForNameResponse), (LspExtExpandMacro, LspExtExpandMacroResponse), (LspExtOpenDocs, LspExtOpenDocsResponse), (SetRoomParticipantRole, Ack), @@ -714,6 +717,7 @@ entity_messages!( OpenServerSettings, GetPermalinkToLine, LanguageServerPromptRequest, + LanguageServerIdForName, GitGetBranches, UpdateGitBranch, ListToolchains,