diff --git a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql index ca840493ad..73d473ab76 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql @@ -173,6 +173,7 @@ CREATE TABLE "language_servers" ( "id" INTEGER NOT NULL, "project_id" INTEGER NOT NULL REFERENCES projects (id) ON DELETE CASCADE, "name" VARCHAR NOT NULL, + "capabilities" TEXT NOT NULL, PRIMARY KEY (project_id, id) ); diff --git a/crates/collab/migrations/20250804080620_language_server_capabilities.sql b/crates/collab/migrations/20250804080620_language_server_capabilities.sql new file mode 100644 index 0000000000..f74f094ed2 --- /dev/null +++ b/crates/collab/migrations/20250804080620_language_server_capabilities.sql @@ -0,0 +1,5 @@ +ALTER TABLE language_servers + ADD COLUMN capabilities TEXT NOT NULL DEFAULT '{}'; + +ALTER TABLE language_servers + ALTER COLUMN capabilities DROP DEFAULT; diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index 8cd1e3ea83..2c22ca2069 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -529,11 +529,17 @@ pub struct RejoinedProject { pub worktrees: Vec, pub updated_repositories: Vec, pub removed_repositories: Vec, - pub language_servers: Vec, + pub language_servers: Vec, } impl RejoinedProject { pub fn to_proto(&self) -> proto::RejoinedProject { + let (language_servers, language_server_capabilities) = self + .language_servers + .clone() + .into_iter() + .map(|server| (server.server, server.capabilities)) + .unzip(); proto::RejoinedProject { id: self.id.to_proto(), worktrees: self @@ -551,7 +557,8 @@ impl RejoinedProject { .iter() .map(|collaborator| collaborator.to_proto()) .collect(), - language_servers: self.language_servers.clone(), + language_servers, + language_server_capabilities, } } } @@ -598,7 +605,7 @@ pub struct Project { pub collaborators: Vec, pub worktrees: BTreeMap, pub repositories: Vec, - pub language_servers: Vec, + pub language_servers: Vec, } pub struct ProjectCollaborator { @@ -623,6 +630,12 @@ impl ProjectCollaborator { } } +#[derive(Debug, Clone)] +pub struct LanguageServer { + pub server: proto::LanguageServer, + pub capabilities: String, +} + #[derive(Debug)] pub struct LeftProject { pub id: ProjectId, diff --git a/crates/collab/src/db/queries/buffers.rs b/crates/collab/src/db/queries/buffers.rs index a288a4e7eb..2e6b4719d1 100644 --- a/crates/collab/src/db/queries/buffers.rs +++ b/crates/collab/src/db/queries/buffers.rs @@ -786,6 +786,32 @@ impl Database { }) .collect()) } + + /// Update language server capabilities for a given id. + pub async fn update_server_capabilities( + &self, + project_id: ProjectId, + server_id: u64, + new_capabilities: String, + ) -> Result<()> { + self.transaction(|tx| { + let new_capabilities = new_capabilities.clone(); + async move { + Ok( + language_server::Entity::update(language_server::ActiveModel { + project_id: ActiveValue::unchanged(project_id), + id: ActiveValue::unchanged(server_id as i64), + capabilities: ActiveValue::set(new_capabilities), + ..Default::default() + }) + .exec(&*tx) + .await?, + ) + } + }) + .await?; + Ok(()) + } } fn operation_to_storage( diff --git a/crates/collab/src/db/queries/projects.rs b/crates/collab/src/db/queries/projects.rs index ba22a7b4e3..31635575a8 100644 --- a/crates/collab/src/db/queries/projects.rs +++ b/crates/collab/src/db/queries/projects.rs @@ -692,6 +692,7 @@ impl Database { project_id: ActiveValue::set(project_id), id: ActiveValue::set(server.id as i64), name: ActiveValue::set(server.name.clone()), + capabilities: ActiveValue::set(update.capabilities.clone()), }) .on_conflict( OnConflict::columns([ @@ -1054,10 +1055,13 @@ impl Database { repositories, language_servers: language_servers .into_iter() - .map(|language_server| proto::LanguageServer { - id: language_server.id as u64, - name: language_server.name, - worktree_id: None, + .map(|language_server| LanguageServer { + server: proto::LanguageServer { + id: language_server.id as u64, + name: language_server.name, + worktree_id: None, + }, + capabilities: language_server.capabilities, }) .collect(), }; diff --git a/crates/collab/src/db/queries/rooms.rs b/crates/collab/src/db/queries/rooms.rs index cb805786dd..c63d7133be 100644 --- a/crates/collab/src/db/queries/rooms.rs +++ b/crates/collab/src/db/queries/rooms.rs @@ -804,10 +804,13 @@ impl Database { .all(tx) .await? .into_iter() - .map(|language_server| proto::LanguageServer { - id: language_server.id as u64, - name: language_server.name, - worktree_id: None, + .map(|language_server| LanguageServer { + server: proto::LanguageServer { + id: language_server.id as u64, + name: language_server.name, + worktree_id: None, + }, + capabilities: language_server.capabilities, }) .collect::>(); diff --git a/crates/collab/src/db/tables/language_server.rs b/crates/collab/src/db/tables/language_server.rs index 9ff8c75fc6..34c7514d91 100644 --- a/crates/collab/src/db/tables/language_server.rs +++ b/crates/collab/src/db/tables/language_server.rs @@ -9,6 +9,7 @@ pub struct Model { #[sea_orm(primary_key)] pub id: i64, pub name: String, + pub capabilities: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 8540a671be..22b21f2c7a 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -1990,12 +1990,19 @@ async fn join_project( } // First, we send the metadata associated with each worktree. + let (language_servers, language_server_capabilities) = project + .language_servers + .clone() + .into_iter() + .map(|server| (server.server, server.capabilities)) + .unzip(); response.send(proto::JoinProjectResponse { project_id: project.id.0 as u64, worktrees: worktrees.clone(), replica_id: replica_id.0 as u32, collaborators: collaborators.clone(), - language_servers: project.language_servers.clone(), + language_servers, + language_server_capabilities, role: project.role.into(), })?; @@ -2054,8 +2061,8 @@ async fn join_project( session.connection_id, proto::UpdateLanguageServer { project_id: project_id.to_proto(), - server_name: Some(language_server.name.clone()), - language_server_id: language_server.id, + server_name: Some(language_server.server.name.clone()), + language_server_id: language_server.server.id, variant: Some( proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated( proto::LspDiskBasedDiagnosticsUpdated {}, @@ -2267,9 +2274,17 @@ async fn update_language_server( session: Session, ) -> Result<()> { let project_id = ProjectId::from_proto(request.project_id); - let project_connection_ids = session - .db() - .await + let db = session.db().await; + + if let Some(proto::update_language_server::Variant::MetadataUpdated(update)) = &request.variant + { + if let Some(capabilities) = update.capabilities.clone() { + db.update_server_capabilities(project_id, request.language_server_id, capabilities) + .await?; + } + } + + let project_connection_ids = db .project_connection_ids(project_id, session.connection_id, true) .await?; broadcast( diff --git a/crates/collab/src/tests/editor_tests.rs b/crates/collab/src/tests/editor_tests.rs index 73ab2b8167..1d28c7f6ef 100644 --- a/crates/collab/src/tests/editor_tests.rs +++ b/crates/collab/src/tests/editor_tests.rs @@ -296,19 +296,28 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu .await; let active_call_a = cx_a.read(ActiveCall::global); + let capabilities = lsp::ServerCapabilities { + completion_provider: Some(lsp::CompletionOptions { + trigger_characters: Some(vec![".".to_string()]), + resolve_provider: Some(true), + ..lsp::CompletionOptions::default() + }), + ..lsp::ServerCapabilities::default() + }; client_a.language_registry().add(rust_lang()); let mut fake_language_servers = client_a.language_registry().register_fake_lsp( "Rust", FakeLspAdapter { - capabilities: lsp::ServerCapabilities { - completion_provider: Some(lsp::CompletionOptions { - trigger_characters: Some(vec![".".to_string()]), - resolve_provider: Some(true), - ..Default::default() - }), - ..Default::default() - }, - ..Default::default() + capabilities: capabilities.clone(), + ..FakeLspAdapter::default() + }, + ); + client_b.language_registry().add(rust_lang()); + client_b.language_registry().register_fake_lsp_adapter( + "Rust", + FakeLspAdapter { + capabilities, + ..FakeLspAdapter::default() }, ); @@ -566,11 +575,14 @@ async fn test_collaborating_with_code_actions( cx_b.update(editor::init); - // 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("Rust", FakeLspAdapter::default()); + client_b.language_registry().add(rust_lang()); + client_b + .language_registry() + .register_fake_lsp("Rust", FakeLspAdapter::default()); client_a .fs() @@ -775,19 +787,27 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T cx_b.update(editor::init); - // Set up a fake language server. + let capabilities = lsp::ServerCapabilities { + rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions { + prepare_provider: Some(true), + work_done_progress_options: Default::default(), + })), + ..lsp::ServerCapabilities::default() + }; client_a.language_registry().add(rust_lang()); let mut fake_language_servers = client_a.language_registry().register_fake_lsp( "Rust", FakeLspAdapter { - capabilities: lsp::ServerCapabilities { - rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions { - prepare_provider: Some(true), - work_done_progress_options: Default::default(), - })), - ..Default::default() - }, - ..Default::default() + capabilities: capabilities.clone(), + ..FakeLspAdapter::default() + }, + ); + client_b.language_registry().add(rust_lang()); + client_b.language_registry().register_fake_lsp_adapter( + "Rust", + FakeLspAdapter { + capabilities, + ..FakeLspAdapter::default() }, ); @@ -818,6 +838,8 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T .downcast::() .unwrap(); let fake_language_server = fake_language_servers.next().await.unwrap(); + cx_a.run_until_parked(); + cx_b.run_until_parked(); // Move cursor to a location that can be renamed. let prepare_rename = editor_b.update_in(cx_b, |editor, window, cx| { @@ -1055,7 +1077,7 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes project_a.read_with(cx_a, |project, cx| { let status = project.language_server_statuses(cx).next().unwrap().1; - assert_eq!(status.name, "the-language-server"); + assert_eq!(status.name.0, "the-language-server"); assert_eq!(status.pending_work.len(), 1); assert_eq!( status.pending_work["the-token"].message.as_ref().unwrap(), @@ -1072,7 +1094,7 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes project_b.read_with(cx_b, |project, cx| { let status = project.language_server_statuses(cx).next().unwrap().1; - assert_eq!(status.name, "the-language-server"); + assert_eq!(status.name.0, "the-language-server"); }); executor.advance_clock(SERVER_PROGRESS_THROTTLE_TIMEOUT); @@ -1089,7 +1111,7 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes project_a.read_with(cx_a, |project, cx| { let status = project.language_server_statuses(cx).next().unwrap().1; - assert_eq!(status.name, "the-language-server"); + assert_eq!(status.name.0, "the-language-server"); assert_eq!(status.pending_work.len(), 1); assert_eq!( status.pending_work["the-token"].message.as_ref().unwrap(), @@ -1099,7 +1121,7 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes project_b.read_with(cx_b, |project, cx| { let status = project.language_server_statuses(cx).next().unwrap().1; - assert_eq!(status.name, "the-language-server"); + assert_eq!(status.name.0, "the-language-server"); assert_eq!(status.pending_work.len(), 1); assert_eq!( status.pending_work["the-token"].message.as_ref().unwrap(), @@ -1422,18 +1444,27 @@ async fn test_on_input_format_from_guest_to_host( .await; let active_call_a = cx_a.read(ActiveCall::global); + let capabilities = lsp::ServerCapabilities { + document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions { + first_trigger_character: ":".to_string(), + more_trigger_character: Some(vec![">".to_string()]), + }), + ..lsp::ServerCapabilities::default() + }; client_a.language_registry().add(rust_lang()); let mut fake_language_servers = client_a.language_registry().register_fake_lsp( "Rust", FakeLspAdapter { - capabilities: lsp::ServerCapabilities { - document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions { - first_trigger_character: ":".to_string(), - more_trigger_character: Some(vec![">".to_string()]), - }), - ..Default::default() - }, - ..Default::default() + capabilities: capabilities.clone(), + ..FakeLspAdapter::default() + }, + ); + client_b.language_registry().add(rust_lang()); + client_b.language_registry().register_fake_lsp_adapter( + "Rust", + FakeLspAdapter { + capabilities, + ..FakeLspAdapter::default() }, ); @@ -1588,16 +1619,24 @@ async fn test_mutual_editor_inlay_hint_cache_update( }); }); + let capabilities = lsp::ServerCapabilities { + inlay_hint_provider: Some(lsp::OneOf::Left(true)), + ..lsp::ServerCapabilities::default() + }; 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 { - capabilities: lsp::ServerCapabilities { - inlay_hint_provider: Some(lsp::OneOf::Left(true)), - ..Default::default() - }, - ..Default::default() + capabilities: capabilities.clone(), + ..FakeLspAdapter::default() + }, + ); + client_b.language_registry().add(rust_lang()); + client_b.language_registry().register_fake_lsp_adapter( + "Rust", + FakeLspAdapter { + capabilities, + ..FakeLspAdapter::default() }, ); @@ -1830,16 +1869,24 @@ async fn test_inlay_hint_refresh_is_forwarded( }); }); + let capabilities = lsp::ServerCapabilities { + inlay_hint_provider: Some(lsp::OneOf::Left(true)), + ..lsp::ServerCapabilities::default() + }; 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 { - capabilities: lsp::ServerCapabilities { - inlay_hint_provider: Some(lsp::OneOf::Left(true)), - ..Default::default() - }, - ..Default::default() + capabilities: capabilities.clone(), + ..FakeLspAdapter::default() + }, + ); + client_b.language_registry().add(rust_lang()); + client_b.language_registry().register_fake_lsp_adapter( + "Rust", + FakeLspAdapter { + capabilities, + ..FakeLspAdapter::default() }, ); @@ -2004,15 +2051,23 @@ async fn test_lsp_document_color(cx_a: &mut TestAppContext, cx_b: &mut TestAppCo }); }); + let capabilities = lsp::ServerCapabilities { + color_provider: Some(lsp::ColorProviderCapability::Simple(true)), + ..lsp::ServerCapabilities::default() + }; 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 { - capabilities: lsp::ServerCapabilities { - color_provider: Some(lsp::ColorProviderCapability::Simple(true)), - ..lsp::ServerCapabilities::default() - }, + capabilities: capabilities.clone(), + ..FakeLspAdapter::default() + }, + ); + client_b.language_registry().add(rust_lang()); + client_b.language_registry().register_fake_lsp_adapter( + "Rust", + FakeLspAdapter { + capabilities, ..FakeLspAdapter::default() }, ); @@ -2063,6 +2118,8 @@ async fn test_lsp_document_color(cx_a: &mut TestAppContext, cx_b: &mut TestAppCo .unwrap(); let fake_language_server = fake_language_servers.next().await.unwrap(); + cx_a.run_until_parked(); + cx_b.run_until_parked(); let requests_made = Arc::new(AtomicUsize::new(0)); let closure_requests_made = Arc::clone(&requests_made); @@ -2264,24 +2321,32 @@ async fn test_lsp_pull_diagnostics( cx_a.update(editor::init); cx_b.update(editor::init); + let capabilities = lsp::ServerCapabilities { + diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options( + lsp::DiagnosticOptions { + identifier: Some("test-pulls".to_string()), + inter_file_dependencies: true, + workspace_diagnostics: true, + work_done_progress_options: lsp::WorkDoneProgressOptions { + work_done_progress: None, + }, + }, + )), + ..lsp::ServerCapabilities::default() + }; 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 { - capabilities: lsp::ServerCapabilities { - diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options( - lsp::DiagnosticOptions { - identifier: Some("test-pulls".to_string()), - inter_file_dependencies: true, - workspace_diagnostics: true, - work_done_progress_options: lsp::WorkDoneProgressOptions { - work_done_progress: None, - }, - }, - )), - ..lsp::ServerCapabilities::default() - }, + capabilities: capabilities.clone(), + ..FakeLspAdapter::default() + }, + ); + client_b.language_registry().add(rust_lang()); + client_b.language_registry().register_fake_lsp_adapter( + "Rust", + FakeLspAdapter { + capabilities, ..FakeLspAdapter::default() }, ); @@ -2334,6 +2399,8 @@ async fn test_lsp_pull_diagnostics( .unwrap(); let fake_language_server = fake_language_servers.next().await.unwrap(); + cx_a.run_until_parked(); + cx_b.run_until_parked(); let expected_push_diagnostic_main_message = "pushed main diagnostic"; let expected_push_diagnostic_lib_message = "pushed lib diagnostic"; let expected_pull_diagnostic_main_message = "pulled main diagnostic"; @@ -2689,6 +2756,7 @@ async fn test_lsp_pull_diagnostics( .unwrap() .downcast::() .unwrap(); + cx_b.run_until_parked(); pull_diagnostics_handle.next().await.unwrap(); assert_eq!( diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index aea359d75b..5a2c40b890 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -4778,10 +4778,27 @@ async fn test_definition( .await; let active_call_a = cx_a.read(ActiveCall::global); - let mut fake_language_servers = client_a - .language_registry() - .register_fake_lsp("Rust", Default::default()); + let capabilities = lsp::ServerCapabilities { + definition_provider: Some(OneOf::Left(true)), + type_definition_provider: Some(lsp::TypeDefinitionProviderCapability::Simple(true)), + ..lsp::ServerCapabilities::default() + }; client_a.language_registry().add(rust_lang()); + let mut fake_language_servers = client_a.language_registry().register_fake_lsp( + "Rust", + FakeLspAdapter { + capabilities: capabilities.clone(), + ..FakeLspAdapter::default() + }, + ); + client_b.language_registry().add(rust_lang()); + client_b.language_registry().register_fake_lsp_adapter( + "Rust", + FakeLspAdapter { + capabilities, + ..FakeLspAdapter::default() + }, + ); client_a .fs() @@ -4827,13 +4844,19 @@ async fn test_definition( ))) }, ); + cx_a.run_until_parked(); + cx_b.run_until_parked(); let definitions_1 = project_b .update(cx_b, |p, cx| p.definitions(&buffer_b, 23, cx)) .await .unwrap(); cx_b.read(|cx| { - assert_eq!(definitions_1.len(), 1); + assert_eq!( + definitions_1.len(), + 1, + "Unexpected definitions: {definitions_1:?}" + ); assert_eq!(project_b.read(cx).worktrees(cx).count(), 2); let target_buffer = definitions_1[0].target.buffer.read(cx); assert_eq!( @@ -4901,7 +4924,11 @@ async fn test_definition( .await .unwrap(); cx_b.read(|cx| { - assert_eq!(type_definitions.len(), 1); + assert_eq!( + type_definitions.len(), + 1, + "Unexpected type definitions: {type_definitions:?}" + ); let target_buffer = type_definitions[0].target.buffer.read(cx); assert_eq!(target_buffer.text(), "type T2 = usize;"); assert_eq!( @@ -4925,16 +4952,26 @@ async fn test_references( .await; let active_call_a = cx_a.read(ActiveCall::global); + let capabilities = lsp::ServerCapabilities { + references_provider: Some(lsp::OneOf::Left(true)), + ..lsp::ServerCapabilities::default() + }; client_a.language_registry().add(rust_lang()); let mut fake_language_servers = client_a.language_registry().register_fake_lsp( "Rust", FakeLspAdapter { name: "my-fake-lsp-adapter", - capabilities: lsp::ServerCapabilities { - references_provider: Some(lsp::OneOf::Left(true)), - ..Default::default() - }, - ..Default::default() + capabilities: capabilities.clone(), + ..FakeLspAdapter::default() + }, + ); + client_b.language_registry().add(rust_lang()); + client_b.language_registry().register_fake_lsp_adapter( + "Rust", + FakeLspAdapter { + name: "my-fake-lsp-adapter", + capabilities: capabilities, + ..FakeLspAdapter::default() }, ); @@ -4989,6 +5026,8 @@ async fn test_references( } } }); + cx_a.run_until_parked(); + cx_b.run_until_parked(); let references = project_b.update(cx_b, |p, cx| p.references(&buffer_b, 7, cx)); @@ -4996,7 +5035,7 @@ async fn test_references( executor.run_until_parked(); project_b.read_with(cx_b, |project, cx| { let status = project.language_server_statuses(cx).next().unwrap().1; - assert_eq!(status.name, "my-fake-lsp-adapter"); + assert_eq!(status.name.0, "my-fake-lsp-adapter"); assert_eq!( status.pending_work.values().next().unwrap().message, Some("Finding references...".into()) @@ -5054,7 +5093,7 @@ async fn test_references( executor.run_until_parked(); project_b.read_with(cx_b, |project, cx| { let status = project.language_server_statuses(cx).next().unwrap().1; - assert_eq!(status.name, "my-fake-lsp-adapter"); + assert_eq!(status.name.0, "my-fake-lsp-adapter"); assert_eq!( status.pending_work.values().next().unwrap().message, Some("Finding references...".into()) @@ -5204,10 +5243,26 @@ async fn test_document_highlights( ) .await; - let mut fake_language_servers = client_a - .language_registry() - .register_fake_lsp("Rust", Default::default()); client_a.language_registry().add(rust_lang()); + let capabilities = lsp::ServerCapabilities { + document_highlight_provider: Some(lsp::OneOf::Left(true)), + ..lsp::ServerCapabilities::default() + }; + let mut fake_language_servers = client_a.language_registry().register_fake_lsp( + "Rust", + FakeLspAdapter { + capabilities: capabilities.clone(), + ..FakeLspAdapter::default() + }, + ); + client_b.language_registry().add(rust_lang()); + client_b.language_registry().register_fake_lsp_adapter( + "Rust", + FakeLspAdapter { + capabilities, + ..FakeLspAdapter::default() + }, + ); let (project_a, worktree_id) = client_a.build_local_project(path!("/root-1"), cx_a).await; let project_id = active_call_a @@ -5256,6 +5311,8 @@ async fn test_document_highlights( ])) }, ); + cx_a.run_until_parked(); + cx_b.run_until_parked(); let highlights = project_b .update(cx_b, |p, cx| p.document_highlights(&buffer_b, 34, cx)) @@ -5306,30 +5363,49 @@ async fn test_lsp_hover( client_a.language_registry().add(rust_lang()); let language_server_names = ["rust-analyzer", "CrabLang-ls"]; + let capabilities_1 = lsp::ServerCapabilities { + hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), + ..lsp::ServerCapabilities::default() + }; + let capabilities_2 = 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() - }, + name: language_server_names[0], + capabilities: capabilities_1.clone(), ..FakeLspAdapter::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() - }, + name: language_server_names[1], + capabilities: capabilities_2.clone(), ..FakeLspAdapter::default() }, ), ]; + client_b.language_registry().add(rust_lang()); + client_b.language_registry().register_fake_lsp_adapter( + "Rust", + FakeLspAdapter { + name: language_server_names[0], + capabilities: capabilities_1, + ..FakeLspAdapter::default() + }, + ); + client_b.language_registry().register_fake_lsp_adapter( + "Rust", + FakeLspAdapter { + name: language_server_names[1], + capabilities: capabilities_2, + ..FakeLspAdapter::default() + }, + ); let (project_a, worktree_id) = client_a.build_local_project(path!("/root-1"), cx_a).await; let project_id = active_call_a @@ -5423,6 +5499,8 @@ async fn test_lsp_hover( unexpected => panic!("Unexpected server name: {unexpected}"), } } + cx_a.run_until_parked(); + cx_b.run_until_parked(); // Request hover information as the guest. let mut hovers = project_b @@ -5605,10 +5683,26 @@ async fn test_open_buffer_while_getting_definition_pointing_to_it( .await; let active_call_a = cx_a.read(ActiveCall::global); + let capabilities = lsp::ServerCapabilities { + definition_provider: Some(OneOf::Left(true)), + ..lsp::ServerCapabilities::default() + }; client_a.language_registry().add(rust_lang()); - let mut fake_language_servers = client_a - .language_registry() - .register_fake_lsp("Rust", Default::default()); + let mut fake_language_servers = client_a.language_registry().register_fake_lsp( + "Rust", + FakeLspAdapter { + capabilities: capabilities.clone(), + ..FakeLspAdapter::default() + }, + ); + client_b.language_registry().add(rust_lang()); + client_b.language_registry().register_fake_lsp_adapter( + "Rust", + FakeLspAdapter { + capabilities, + ..FakeLspAdapter::default() + }, + ); client_a .fs() @@ -5649,6 +5743,8 @@ async fn test_open_buffer_while_getting_definition_pointing_to_it( let definitions; let buffer_b2; if rng.r#gen() { + cx_a.run_until_parked(); + cx_b.run_until_parked(); definitions = project_b.update(cx_b, |p, cx| p.definitions(&buffer_b1, 23, cx)); (buffer_b2, _) = project_b .update(cx_b, |p, cx| { @@ -5663,11 +5759,17 @@ async fn test_open_buffer_while_getting_definition_pointing_to_it( }) .await .unwrap(); + cx_a.run_until_parked(); + cx_b.run_until_parked(); definitions = project_b.update(cx_b, |p, cx| p.definitions(&buffer_b1, 23, cx)); } let definitions = definitions.await.unwrap(); - assert_eq!(definitions.len(), 1); + assert_eq!( + definitions.len(), + 1, + "Unexpected definitions: {definitions:?}" + ); assert_eq!(definitions[0].target.buffer, buffer_b2); } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 2912708b56..ff9b703d66 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -134,7 +134,7 @@ use language::{ use linked_editing_ranges::refresh_linked_ranges; use lsp::{ CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode, - LanguageServerId, LanguageServerName, + LanguageServerId, }; use lsp_colors::LspColorData; use markdown::Markdown; @@ -1864,7 +1864,6 @@ impl Editor { editor.tasks_update_task = Some(editor.refresh_runnables(window, cx)); } - editor.update_lsp_data(true, None, window, cx); } project::Event::SnippetEdit(id, snippet_edits) => { if let Some(buffer) = editor.buffer.read(cx).buffer(*id) { @@ -1886,6 +1885,11 @@ impl Editor { } } } + project::Event::LanguageServerBufferRegistered { buffer_id, .. } => { + if editor.buffer().read(cx).buffer(*buffer_id).is_some() { + editor.update_lsp_data(false, Some(*buffer_id), window, cx); + } + } _ => {} }, )); @@ -15846,7 +15850,7 @@ impl Editor { let language_server_name = project .language_server_statuses(cx) .find(|(id, _)| server_id == *id) - .map(|(_, status)| LanguageServerName::from(status.name.as_str())); + .map(|(_, status)| status.name.clone()); language_server_name.map(|language_server_name| { project.open_local_buffer_via_lsp( lsp_location.uri.clone(), diff --git a/crates/editor/src/linked_editing_ranges.rs b/crates/editor/src/linked_editing_ranges.rs index 7c2672fc0d..a185de33ca 100644 --- a/crates/editor/src/linked_editing_ranges.rs +++ b/crates/editor/src/linked_editing_ranges.rs @@ -95,7 +95,7 @@ pub(super) fn refresh_linked_ranges( let snapshot = buffer.read(cx).snapshot(); let buffer_id = buffer.read(cx).remote_id(); - let linked_edits_task = project.linked_edit(buffer, *start, cx); + let linked_edits_task = project.linked_edits(buffer, *start, cx); let highlights = move || async move { let edits = linked_edits_task.await.log_err()?; // Find the range containing our current selection. diff --git a/crates/language/src/language_registry.rs b/crates/language/src/language_registry.rs index ab3c0f9b37..85123d2373 100644 --- a/crates/language/src/language_registry.rs +++ b/crates/language/src/language_registry.rs @@ -411,30 +411,6 @@ impl LanguageRegistry { cached } - pub fn get_or_register_lsp_adapter( - &self, - language_name: LanguageName, - server_name: LanguageServerName, - build_adapter: impl FnOnce() -> Arc + 'static, - ) -> Arc { - let registered = self - .state - .write() - .lsp_adapters - .entry(language_name.clone()) - .or_default() - .iter() - .find(|cached_adapter| cached_adapter.name == server_name) - .cloned(); - - if let Some(found) = registered { - found - } else { - let adapter = build_adapter(); - self.register_lsp_adapter(language_name, 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))] diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 2fd61ea0b2..f8e69e2185 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -2154,6 +2154,16 @@ impl LspCommand for GetHover { } } +impl GetCompletions { + pub fn can_resolve_completions(capabilities: &lsp::ServerCapabilities) -> bool { + capabilities + .completion_provider + .as_ref() + .and_then(|options| options.resolve_provider) + .unwrap_or(false) + } +} + #[async_trait(?Send)] impl LspCommand for GetCompletions { type Response = CoreCompletionResponse; @@ -2762,6 +2772,23 @@ impl GetCodeActions { } } +impl OnTypeFormatting { + pub fn supports_on_type_formatting(trigger: &str, capabilities: &ServerCapabilities) -> bool { + let Some(on_type_formatting_options) = &capabilities.document_on_type_formatting_provider + else { + return false; + }; + on_type_formatting_options + .first_trigger_character + .contains(trigger) + || on_type_formatting_options + .more_trigger_character + .iter() + .flatten() + .any(|chars| chars.contains(trigger)) + } +} + #[async_trait(?Send)] impl LspCommand for OnTypeFormatting { type Response = Option; @@ -2773,20 +2800,7 @@ impl LspCommand for OnTypeFormatting { } fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool { - let Some(on_type_formatting_options) = &capabilities - .server_capabilities - .document_on_type_formatting_provider - else { - return false; - }; - on_type_formatting_options - .first_trigger_character - .contains(&self.trigger) - || on_type_formatting_options - .more_trigger_character - .iter() - .flatten() - .any(|chars| chars.contains(&self.trigger)) + Self::supports_on_type_formatting(&self.trigger, &capabilities.server_capabilities) } fn to_lsp( @@ -4221,8 +4235,9 @@ impl LspCommand for GetDocumentColor { server_capabilities .server_capabilities .color_provider + .as_ref() .is_some_and(|capability| match capability { - lsp::ColorProviderCapability::Simple(supported) => supported, + lsp::ColorProviderCapability::Simple(supported) => *supported, lsp::ColorProviderCapability::ColorProvider(..) => true, lsp::ColorProviderCapability::Options(..) => true, }) diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 6122204991..6d448a6fea 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -58,12 +58,12 @@ use language::{ range_from_lsp, range_to_lsp, }; use lsp::{ - CodeActionKind, CompletionContext, DiagnosticSeverity, DiagnosticTag, - DidChangeWatchedFilesRegistrationOptions, Edit, FileOperationFilter, FileOperationPatternKind, - FileOperationRegistrationOptions, FileRename, FileSystemWatcher, LanguageServer, - LanguageServerBinary, LanguageServerBinaryOptions, LanguageServerId, LanguageServerName, - LanguageServerSelector, LspRequestFuture, MessageActionItem, MessageType, OneOf, - RenameFilesParams, SymbolKind, TextEdit, WillRenameFiles, WorkDoneProgressCancelParams, + AdapterServerCapabilities, CodeActionKind, CompletionContext, DiagnosticSeverity, + DiagnosticTag, DidChangeWatchedFilesRegistrationOptions, Edit, FileOperationFilter, + FileOperationPatternKind, FileOperationRegistrationOptions, FileRename, FileSystemWatcher, + LanguageServer, LanguageServerBinary, LanguageServerBinaryOptions, LanguageServerId, + LanguageServerName, LanguageServerSelector, LspRequestFuture, MessageActionItem, MessageType, + OneOf, RenameFilesParams, SymbolKind, TextEdit, WillRenameFiles, WorkDoneProgressCancelParams, WorkspaceFolder, notification::DidRenameFiles, }; use node_runtime::read_package_installed_version; @@ -622,7 +622,7 @@ impl LocalLspStore { .on_request::({ let this = this.clone(); move |params, cx| { - let this = this.clone(); + let lsp_store = this.clone(); let mut cx = cx.clone(); async move { for reg in params.registrations { @@ -630,7 +630,7 @@ impl LocalLspStore { "workspace/didChangeWatchedFiles" => { if let Some(options) = reg.register_options { let options = serde_json::from_value(options)?; - this.update(&mut cx, |this, cx| { + lsp_store.update(&mut cx, |this, cx| { this.as_local_mut()?.on_lsp_did_change_watched_files( server_id, ®.id, options, cx, ); @@ -639,8 +639,9 @@ impl LocalLspStore { } } "textDocument/rangeFormatting" => { - this.read_with(&mut cx, |this, _| { - if let Some(server) = this.language_server_for_id(server_id) + lsp_store.update(&mut cx, |lsp_store, cx| { + if let Some(server) = + lsp_store.language_server_for_id(server_id) { let options = reg .register_options @@ -659,14 +660,16 @@ impl LocalLspStore { server.update_capabilities(|capabilities| { capabilities.document_range_formatting_provider = Some(provider); - }) + }); + notify_server_capabilities_updated(&server, cx); } anyhow::Ok(()) })??; } "textDocument/onTypeFormatting" => { - this.read_with(&mut cx, |this, _| { - if let Some(server) = this.language_server_for_id(server_id) + lsp_store.update(&mut cx, |lsp_store, cx| { + if let Some(server) = + lsp_store.language_server_for_id(server_id) { let options = reg .register_options @@ -683,15 +686,17 @@ impl LocalLspStore { capabilities .document_on_type_formatting_provider = Some(options); - }) + }); + notify_server_capabilities_updated(&server, cx); } } anyhow::Ok(()) })??; } "textDocument/formatting" => { - this.read_with(&mut cx, |this, _| { - if let Some(server) = this.language_server_for_id(server_id) + lsp_store.update(&mut cx, |lsp_store, cx| { + if let Some(server) = + lsp_store.language_server_for_id(server_id) { let options = reg .register_options @@ -710,7 +715,8 @@ impl LocalLspStore { server.update_capabilities(|capabilities| { capabilities.document_formatting_provider = Some(provider); - }) + }); + notify_server_capabilities_updated(&server, cx); } anyhow::Ok(()) })??; @@ -719,8 +725,9 @@ impl LocalLspStore { // Ignore payload since we notify clients of setting changes unconditionally, relying on them pulling the latest settings. } "textDocument/rename" => { - this.read_with(&mut cx, |this, _| { - if let Some(server) = this.language_server_for_id(server_id) + lsp_store.update(&mut cx, |lsp_store, cx| { + if let Some(server) = + lsp_store.language_server_for_id(server_id) { let options = reg .register_options @@ -737,7 +744,8 @@ impl LocalLspStore { server.update_capabilities(|capabilities| { capabilities.rename_provider = Some(options); - }) + }); + notify_server_capabilities_updated(&server, cx); } anyhow::Ok(()) })??; @@ -755,14 +763,15 @@ impl LocalLspStore { .on_request::({ let this = this.clone(); move |params, cx| { - let this = this.clone(); + let lsp_store = this.clone(); let mut cx = cx.clone(); async move { for unreg in params.unregisterations.iter() { match unreg.method.as_str() { "workspace/didChangeWatchedFiles" => { - this.update(&mut cx, |this, cx| { - this.as_local_mut()? + lsp_store.update(&mut cx, |lsp_store, cx| { + lsp_store + .as_local_mut()? .on_lsp_unregister_did_change_watched_files( server_id, &unreg.id, cx, ); @@ -773,44 +782,52 @@ impl LocalLspStore { // Ignore payload since we notify clients of setting changes unconditionally, relying on them pulling the latest settings. } "textDocument/rename" => { - this.read_with(&mut cx, |this, _| { - if let Some(server) = this.language_server_for_id(server_id) + lsp_store.update(&mut cx, |lsp_store, cx| { + if let Some(server) = + lsp_store.language_server_for_id(server_id) { server.update_capabilities(|capabilities| { capabilities.rename_provider = None - }) + }); + notify_server_capabilities_updated(&server, cx); } })?; } "textDocument/rangeFormatting" => { - this.read_with(&mut cx, |this, _| { - if let Some(server) = this.language_server_for_id(server_id) + lsp_store.update(&mut cx, |lsp_store, cx| { + if let Some(server) = + lsp_store.language_server_for_id(server_id) { server.update_capabilities(|capabilities| { capabilities.document_range_formatting_provider = None - }) + }); + notify_server_capabilities_updated(&server, cx); } })?; } "textDocument/onTypeFormatting" => { - this.read_with(&mut cx, |this, _| { - if let Some(server) = this.language_server_for_id(server_id) + lsp_store.update(&mut cx, |lsp_store, cx| { + if let Some(server) = + lsp_store.language_server_for_id(server_id) { server.update_capabilities(|capabilities| { capabilities.document_on_type_formatting_provider = None; - }) + }); + notify_server_capabilities_updated(&server, cx); } })?; } "textDocument/formatting" => { - this.read_with(&mut cx, |this, _| { - if let Some(server) = this.language_server_for_id(server_id) + lsp_store.update(&mut cx, |lsp_store, cx| { + if let Some(server) = + lsp_store.language_server_for_id(server_id) { server.update_capabilities(|capabilities| { capabilities.document_formatting_provider = None; - }) + }); + notify_server_capabilities_updated(&server, cx); } })?; } @@ -2426,7 +2443,6 @@ impl LocalLspStore { let server_id = server_node.server_id_or_init( |LaunchDisposition { server_name, - path, settings, }| { @@ -2468,18 +2484,6 @@ impl LocalLspStore { } }; - let lsp_store = self.weak.clone(); - let server_name = server_node.name(); - let buffer_abs_path = abs_path.to_string_lossy().to_string(); - cx.defer(move |cx| { - lsp_store.update(cx, |_, cx| cx.emit(LspStoreEvent::LanguageServerUpdate { - language_server_id: server_id, - name: server_name, - message: proto::update_language_server::Variant::RegisteredForBuffer(proto::RegisteredForBuffer { - buffer_abs_path, - }) - })).ok(); - }); server_id }, )?; @@ -2515,11 +2519,13 @@ impl LocalLspStore { snapshot: initial_snapshot.clone(), }; + let mut registered = false; self.buffer_snapshots .entry(buffer_id) .or_default() .entry(server.server_id()) .or_insert_with(|| { + registered = true; server.register_buffer( uri.clone(), adapter.language_id(&language.name()), @@ -2534,15 +2540,18 @@ impl LocalLspStore { .entry(buffer_id) .or_default() .insert(server.server_id()); - cx.emit(LspStoreEvent::LanguageServerUpdate { - language_server_id: server.server_id(), - name: None, - message: proto::update_language_server::Variant::RegisteredForBuffer( - proto::RegisteredForBuffer { - buffer_abs_path: abs_path.to_string_lossy().to_string(), - }, - ), - }); + if registered { + cx.emit(LspStoreEvent::LanguageServerUpdate { + language_server_id: server.server_id(), + name: None, + message: proto::update_language_server::Variant::RegisteredForBuffer( + proto::RegisteredForBuffer { + buffer_abs_path: abs_path.to_string_lossy().to_string(), + buffer_id: buffer_id.to_proto(), + }, + ), + }); + } } } @@ -3494,6 +3503,20 @@ impl LocalLspStore { } } +fn notify_server_capabilities_updated(server: &LanguageServer, cx: &mut Context) { + if let Some(capabilities) = serde_json::to_string(&server.capabilities()).ok() { + cx.emit(LspStoreEvent::LanguageServerUpdate { + language_server_id: server.server_id(), + name: Some(server.name()), + message: proto::update_language_server::Variant::MetadataUpdated( + proto::ServerMetadataUpdated { + capabilities: Some(capabilities), + }, + ), + }); + } +} + #[derive(Debug)] pub struct FormattableBuffer { handle: Entity, @@ -3533,6 +3556,7 @@ pub struct LspStore { _maintain_buffer_languages: Task<()>, diagnostic_summaries: HashMap, HashMap>>, + pub(super) lsp_server_capabilities: HashMap, lsp_document_colors: HashMap, lsp_code_lens: HashMap, } @@ -3604,7 +3628,7 @@ pub enum LspStoreEvent { #[derive(Clone, Debug, Serialize)] pub struct LanguageServerStatus { - pub name: String, + pub name: LanguageServerName, pub pending_work: BTreeMap, pub has_pending_diagnostic_updates: bool, progress_tokens: HashSet, @@ -3795,6 +3819,7 @@ impl LspStore { language_server_statuses: Default::default(), nonce: StdRng::from_entropy().r#gen(), diagnostic_summaries: HashMap::default(), + lsp_server_capabilities: HashMap::default(), lsp_document_colors: HashMap::default(), lsp_code_lens: HashMap::default(), active_entry: None, @@ -3811,6 +3836,9 @@ impl LspStore { request: R, cx: &mut Context, ) -> Task::Response>> { + if !self.is_capable_for_proto_request(&buffer, &request, cx) { + return Task::ready(Ok(R::Response::default())); + } let message = request.to_proto(upstream_project_id, buffer.read(cx)); cx.spawn(async move |this, cx| { let response = client.request(message).await?; @@ -3853,6 +3881,7 @@ impl LspStore { language_server_statuses: Default::default(), nonce: StdRng::from_entropy().r#gen(), diagnostic_summaries: HashMap::default(), + lsp_server_capabilities: HashMap::default(), lsp_document_colors: HashMap::default(), lsp_code_lens: HashMap::default(), active_entry: None, @@ -4428,20 +4457,73 @@ impl LspStore { } } - pub fn request_lsp( + // TODO: remove MultiLspQuery: instead, the proto handler should pick appropriate server(s) + // Then, use `send_lsp_proto_request` or analogue for most of the LSP proto requests and inline this check inside + fn is_capable_for_proto_request( + &self, + buffer: &Entity, + request: &R, + cx: &Context, + ) -> bool + where + R: LspCommand, + { + self.check_if_capable_for_proto_request( + buffer, + |capabilities| { + request.check_capabilities(AdapterServerCapabilities { + server_capabilities: capabilities.clone(), + code_action_kinds: None, + }) + }, + cx, + ) + } + + fn check_if_capable_for_proto_request( + &self, + buffer: &Entity, + check: F, + cx: &Context, + ) -> bool + where + F: Fn(&lsp::ServerCapabilities) -> bool, + { + let Some(language) = buffer.read(cx).language().cloned() else { + return false; + }; + let relevant_language_servers = self + .languages + .lsp_adapters(&language.name()) + .into_iter() + .map(|lsp_adapter| lsp_adapter.name()) + .collect::>(); + self.language_server_statuses + .iter() + .filter_map(|(server_id, server_status)| { + relevant_language_servers + .contains(&server_status.name) + .then_some(server_id) + }) + .filter_map(|server_id| self.lsp_server_capabilities.get(&server_id)) + .any(check) + } + + pub fn request_lsp( &mut self, - buffer_handle: Entity, + buffer: Entity, server: LanguageServerToQuery, request: R, cx: &mut Context, ) -> Task> where + R: LspCommand, ::Result: Send, ::Params: Send, { if let Some((upstream_client, upstream_project_id)) = self.upstream_client() { return self.send_lsp_proto_request( - buffer_handle, + buffer, upstream_client, upstream_project_id, request, @@ -4449,7 +4531,7 @@ impl LspStore { ); } - let Some(language_server) = buffer_handle.update(cx, |buffer, cx| match server { + let Some(language_server) = buffer.update(cx, |buffer, cx| match server { LanguageServerToQuery::FirstCapable => self.as_local().and_then(|local| { local .language_servers_for_buffer(buffer, cx) @@ -4469,8 +4551,7 @@ impl LspStore { return Task::ready(Ok(Default::default())); }; - let buffer = buffer_handle.read(cx); - let file = File::from_dyn(buffer.file()).and_then(File::as_local); + let file = File::from_dyn(buffer.read(cx).file()).and_then(File::as_local); let Some(file) = file else { return Task::ready(Ok(Default::default())); @@ -4478,7 +4559,7 @@ impl LspStore { let lsp_params = match request.to_lsp_params_or_response( &file.abs_path(cx), - buffer, + buffer.read(cx), &language_server, cx, ) { @@ -4554,7 +4635,7 @@ impl LspStore { .response_from_lsp( response, this.upgrade().context("no app context")?, - buffer_handle, + buffer, language_server.server_id(), cx.clone(), ) @@ -4624,7 +4705,8 @@ impl LspStore { ) }) { let buffer = buffer_handle.read(cx); - if !local.registered_buffers.contains_key(&buffer.remote_id()) { + let buffer_id = buffer.remote_id(); + if !local.registered_buffers.contains_key(&buffer_id) { continue; } if let Some((file, language)) = File::from_dyn(buffer.file()) @@ -4725,6 +4807,7 @@ impl LspStore { proto::update_language_server::Variant::RegisteredForBuffer( proto::RegisteredForBuffer { buffer_abs_path: abs_path.to_string_lossy().to_string(), + buffer_id: buffer_id.to_proto(), }, ), }); @@ -4900,15 +4983,20 @@ impl LspStore { pub fn resolve_inlay_hint( &self, - hint: InlayHint, - buffer_handle: Entity, + mut hint: InlayHint, + buffer: Entity, server_id: LanguageServerId, cx: &mut Context, ) -> Task> { if let Some((upstream_client, project_id)) = self.upstream_client() { + if !self.check_if_capable_for_proto_request(&buffer, InlayHints::can_resolve_inlays, cx) + { + hint.resolve_state = ResolveState::Resolved; + return Task::ready(Ok(hint)); + } let request = proto::ResolveInlayHint { project_id, - buffer_id: buffer_handle.read(cx).remote_id().into(), + buffer_id: buffer.read(cx).remote_id().into(), language_server_id: server_id.0 as u64, hint: Some(InlayHints::project_to_proto_hint(hint.clone())), }; @@ -4924,7 +5012,7 @@ impl LspStore { } }) } else { - let Some(lang_server) = buffer_handle.update(cx, |buffer, cx| { + let Some(lang_server) = buffer.update(cx, |buffer, cx| { self.language_server_for_local_buffer(buffer, server_id, cx) .map(|(_, server)| server.clone()) }) else { @@ -4933,7 +5021,7 @@ impl LspStore { if !InlayHints::can_resolve_inlays(&lang_server.capabilities()) { return Task::ready(Ok(hint)); } - let buffer_snapshot = buffer_handle.read(cx).snapshot(); + let buffer_snapshot = buffer.read(cx).snapshot(); cx.spawn(async move |_, cx| { let resolve_task = lang_server.request::( InlayHints::project_to_lsp_hint(hint, &buffer_snapshot), @@ -4944,7 +5032,7 @@ impl LspStore { .context("inlay hint resolve LSP request")?; let resolved_hint = InlayHints::lsp_to_project_hint( resolved_hint, - &buffer_handle, + &buffer, server_id, ResolveState::Resolved, false, @@ -5055,7 +5143,7 @@ impl LspStore { } } - pub(crate) fn linked_edit( + pub(crate) fn linked_edits( &mut self, buffer: &Entity, position: Anchor, @@ -5097,7 +5185,7 @@ impl LspStore { }) == Some(true) }) else { - return Task::ready(Ok(vec![])); + return Task::ready(Ok(Vec::new())); }; self.request_lsp( @@ -5116,6 +5204,15 @@ impl LspStore { cx: &mut Context, ) -> Task>> { if let Some((client, project_id)) = self.upstream_client() { + if !self.check_if_capable_for_proto_request( + &buffer, + |capabilities| { + OnTypeFormatting::supports_on_type_formatting(&trigger, capabilities) + }, + cx, + ) { + return Task::ready(Ok(None)); + } let request = proto::OnTypeFormatting { project_id, buffer_id: buffer.read(cx).remote_id().into(), @@ -5227,6 +5324,10 @@ impl LspStore { cx: &mut Context, ) -> Task>> { if let Some((upstream_client, project_id)) = self.upstream_client() { + let request = GetDefinitions { position }; + if !self.is_capable_for_proto_request(buffer_handle, &request, cx) { + return Task::ready(Ok(Vec::new())); + } let request_task = upstream_client.request(proto::MultiLspQuery { buffer_id: buffer_handle.read(cx).remote_id().into(), version: serialize_version(&buffer_handle.read(cx).version()), @@ -5235,7 +5336,7 @@ impl LspStore { proto::AllLanguageServers {}, )), request: Some(proto::multi_lsp_query::Request::GetDefinition( - GetDefinitions { position }.to_proto(project_id, buffer_handle.read(cx)), + request.to_proto(project_id, buffer_handle.read(cx)), )), }); let buffer = buffer_handle.clone(); @@ -5300,6 +5401,10 @@ impl LspStore { cx: &mut Context, ) -> Task>> { if let Some((upstream_client, project_id)) = self.upstream_client() { + let request = GetDeclarations { position }; + if !self.is_capable_for_proto_request(buffer_handle, &request, cx) { + return Task::ready(Ok(Vec::new())); + } let request_task = upstream_client.request(proto::MultiLspQuery { buffer_id: buffer_handle.read(cx).remote_id().into(), version: serialize_version(&buffer_handle.read(cx).version()), @@ -5308,7 +5413,7 @@ impl LspStore { proto::AllLanguageServers {}, )), request: Some(proto::multi_lsp_query::Request::GetDeclaration( - GetDeclarations { position }.to_proto(project_id, buffer_handle.read(cx)), + request.to_proto(project_id, buffer_handle.read(cx)), )), }); let buffer = buffer_handle.clone(); @@ -5368,23 +5473,27 @@ impl LspStore { pub fn type_definitions( &mut self, - buffer_handle: &Entity, + buffer: &Entity, position: PointUtf16, cx: &mut Context, ) -> Task>> { if let Some((upstream_client, project_id)) = self.upstream_client() { + let request = GetTypeDefinitions { position }; + if !self.is_capable_for_proto_request(&buffer, &request, cx) { + return Task::ready(Ok(Vec::new())); + } let request_task = upstream_client.request(proto::MultiLspQuery { - buffer_id: buffer_handle.read(cx).remote_id().into(), - version: serialize_version(&buffer_handle.read(cx).version()), + buffer_id: buffer.read(cx).remote_id().into(), + version: serialize_version(&buffer.read(cx).version()), project_id, strategy: Some(proto::multi_lsp_query::Strategy::All( proto::AllLanguageServers {}, )), request: Some(proto::multi_lsp_query::Request::GetTypeDefinition( - GetTypeDefinitions { position }.to_proto(project_id, buffer_handle.read(cx)), + request.to_proto(project_id, buffer.read(cx)), )), }); - let buffer = buffer_handle.clone(); + let buffer = buffer.clone(); cx.spawn(async move |weak_project, cx| { let Some(project) = weak_project.upgrade() else { return Ok(Vec::new()); @@ -5423,7 +5532,7 @@ impl LspStore { }) } else { let type_definitions_task = self.request_multiple_lsp_locally( - buffer_handle, + buffer, Some(position), GetTypeDefinitions { position }, cx, @@ -5441,23 +5550,27 @@ impl LspStore { pub fn implementations( &mut self, - buffer_handle: &Entity, + buffer: &Entity, position: PointUtf16, cx: &mut Context, ) -> Task>> { if let Some((upstream_client, project_id)) = self.upstream_client() { + let request = GetImplementations { position }; + if !self.is_capable_for_proto_request(buffer, &request, cx) { + return Task::ready(Ok(Vec::new())); + } let request_task = upstream_client.request(proto::MultiLspQuery { - buffer_id: buffer_handle.read(cx).remote_id().into(), - version: serialize_version(&buffer_handle.read(cx).version()), + buffer_id: buffer.read(cx).remote_id().into(), + version: serialize_version(&buffer.read(cx).version()), project_id, strategy: Some(proto::multi_lsp_query::Strategy::All( proto::AllLanguageServers {}, )), request: Some(proto::multi_lsp_query::Request::GetImplementation( - GetImplementations { position }.to_proto(project_id, buffer_handle.read(cx)), + request.to_proto(project_id, buffer.read(cx)), )), }); - let buffer = buffer_handle.clone(); + let buffer = buffer.clone(); cx.spawn(async move |weak_project, cx| { let Some(project) = weak_project.upgrade() else { return Ok(Vec::new()); @@ -5496,7 +5609,7 @@ impl LspStore { }) } else { let implementations_task = self.request_multiple_lsp_locally( - buffer_handle, + buffer, Some(position), GetImplementations { position }, cx, @@ -5514,23 +5627,27 @@ impl LspStore { pub fn references( &mut self, - buffer_handle: &Entity, + buffer: &Entity, position: PointUtf16, cx: &mut Context, ) -> Task>> { if let Some((upstream_client, project_id)) = self.upstream_client() { + let request = GetReferences { position }; + if !self.is_capable_for_proto_request(&buffer, &request, cx) { + return Task::ready(Ok(Vec::new())); + } let request_task = upstream_client.request(proto::MultiLspQuery { - buffer_id: buffer_handle.read(cx).remote_id().into(), - version: serialize_version(&buffer_handle.read(cx).version()), + buffer_id: buffer.read(cx).remote_id().into(), + version: serialize_version(&buffer.read(cx).version()), project_id, strategy: Some(proto::multi_lsp_query::Strategy::All( proto::AllLanguageServers {}, )), request: Some(proto::multi_lsp_query::Request::GetReferences( - GetReferences { position }.to_proto(project_id, buffer_handle.read(cx)), + request.to_proto(project_id, buffer.read(cx)), )), }); - let buffer = buffer_handle.clone(); + let buffer = buffer.clone(); cx.spawn(async move |weak_project, cx| { let Some(project) = weak_project.upgrade() else { return Ok(Vec::new()); @@ -5569,7 +5686,7 @@ impl LspStore { }) } else { let references_task = self.request_multiple_lsp_locally( - buffer_handle, + buffer, Some(position), GetReferences { position }, cx, @@ -5587,28 +5704,31 @@ impl LspStore { pub fn code_actions( &mut self, - buffer_handle: &Entity, + buffer: &Entity, range: Range, kinds: Option>, cx: &mut Context, ) -> Task>> { if let Some((upstream_client, project_id)) = self.upstream_client() { + let request = GetCodeActions { + range: range.clone(), + kinds: kinds.clone(), + }; + if !self.is_capable_for_proto_request(buffer, &request, cx) { + return Task::ready(Ok(Vec::new())); + } let request_task = upstream_client.request(proto::MultiLspQuery { - buffer_id: buffer_handle.read(cx).remote_id().into(), - version: serialize_version(&buffer_handle.read(cx).version()), + buffer_id: buffer.read(cx).remote_id().into(), + version: serialize_version(&buffer.read(cx).version()), project_id, strategy: Some(proto::multi_lsp_query::Strategy::All( proto::AllLanguageServers {}, )), request: Some(proto::multi_lsp_query::Request::GetCodeActions( - GetCodeActions { - range: range.clone(), - kinds: kinds.clone(), - } - .to_proto(project_id, buffer_handle.read(cx)), + request.to_proto(project_id, buffer.read(cx)), )), }); - let buffer = buffer_handle.clone(); + let buffer = buffer.clone(); cx.spawn(async move |weak_project, cx| { let Some(project) = weak_project.upgrade() else { return Ok(Vec::new()); @@ -5650,7 +5770,7 @@ impl LspStore { }) } else { let all_actions_task = self.request_multiple_lsp_locally( - buffer_handle, + buffer, Some(range.start), GetCodeActions { range: range.clone(), @@ -5752,6 +5872,10 @@ impl LspStore { cx: &mut Context, ) -> Task>>> { if let Some((upstream_client, project_id)) = self.upstream_client() { + let request = GetCodeLens; + if !self.is_capable_for_proto_request(buffer, &request, cx) { + return Task::ready(Ok(HashMap::default())); + } let request_task = upstream_client.request(proto::MultiLspQuery { buffer_id: buffer.read(cx).remote_id().into(), version: serialize_version(&buffer.read(cx).version()), @@ -5760,7 +5884,7 @@ impl LspStore { proto::AllLanguageServers {}, )), request: Some(proto::multi_lsp_query::Request::GetCodeLens( - GetCodeLens.to_proto(project_id, buffer.read(cx)), + request.to_proto(project_id, buffer.read(cx)), )), }); let buffer = buffer.clone(); @@ -5844,11 +5968,15 @@ impl LspStore { let language_registry = self.languages.clone(); if let Some((upstream_client, project_id)) = self.upstream_client() { + let request = GetCompletions { position, context }; + if !self.is_capable_for_proto_request(buffer, &request, cx) { + return Task::ready(Ok(Vec::new())); + } let task = self.send_lsp_proto_request( buffer.clone(), upstream_client, project_id, - GetCompletions { position, context }, + request, cx, ); let language = buffer.read(cx).language().cloned(); @@ -5986,11 +6114,17 @@ impl LspStore { cx: &mut Context, ) -> Task> { let client = self.upstream_client(); - let buffer_id = buffer.read(cx).remote_id(); let buffer_snapshot = buffer.read(cx).snapshot(); - cx.spawn(async move |this, cx| { + if !self.check_if_capable_for_proto_request( + &buffer, + GetCompletions::can_resolve_completions, + cx, + ) { + return Task::ready(Ok(false)); + } + cx.spawn(async move |lsp_store, cx| { let mut did_resolve = false; if let Some((client, project_id)) = client { for completion_index in completion_indices { @@ -6027,7 +6161,7 @@ impl LspStore { completion.source.server_id() }; if let Some(server_id) = server_id { - let server_and_adapter = this + let server_and_adapter = lsp_store .read_with(cx, |lsp_store, _| { let server = lsp_store.language_server_for_id(server_id)?; let adapter = @@ -6078,13 +6212,7 @@ impl LspStore { completion_index: usize, ) -> Result<()> { let server_id = server.server_id(); - let can_resolve = server - .capabilities() - .completion_provider - .as_ref() - .and_then(|options| options.resolve_provider) - .unwrap_or(false); - if !can_resolve { + if !GetCompletions::can_resolve_completions(&server.capabilities()) { return Ok(()); } @@ -6435,16 +6563,24 @@ impl LspStore { pub fn pull_diagnostics( &mut self, - buffer_handle: Entity, + buffer: Entity, cx: &mut Context, ) -> Task>> { - let buffer = buffer_handle.read(cx); - let buffer_id = buffer.remote_id(); + let buffer_id = buffer.read(cx).remote_id(); if let Some((client, upstream_project_id)) = self.upstream_client() { + if !self.is_capable_for_proto_request( + &buffer, + &GetDocumentDiagnostics { + previous_result_id: None, + }, + cx, + ) { + return Task::ready(Ok(Vec::new())); + } let request_task = client.request(proto::MultiLspQuery { buffer_id: buffer_id.to_proto(), - version: serialize_version(&buffer_handle.read(cx).version()), + version: serialize_version(&buffer.read(cx).version()), project_id: upstream_project_id, strategy: Some(proto::multi_lsp_query::Strategy::All( proto::AllLanguageServers {}, @@ -6453,7 +6589,7 @@ impl LspStore { proto::GetDocumentDiagnostics { project_id: upstream_project_id, buffer_id: buffer_id.to_proto(), - version: serialize_version(&buffer_handle.read(cx).version()), + version: serialize_version(&buffer.read(cx).version()), }, )), }); @@ -6475,7 +6611,7 @@ impl LspStore { .collect()) }) } else { - let server_ids = buffer_handle.update(cx, |buffer, cx| { + let server_ids = buffer.update(cx, |buffer, cx| { self.language_servers_for_local_buffer(buffer, cx) .map(|(_, server)| server.server_id()) .collect::>() @@ -6485,7 +6621,7 @@ impl LspStore { .map(|server_id| { let result_id = self.result_id(server_id, buffer_id, cx); self.request_lsp( - buffer_handle.clone(), + buffer.clone(), LanguageServerToQuery::Other(server_id), GetDocumentDiagnostics { previous_result_id: result_id, @@ -6507,34 +6643,36 @@ impl LspStore { pub fn inlay_hints( &mut self, - buffer_handle: Entity, + buffer: Entity, range: Range, cx: &mut Context, ) -> Task>> { - let buffer = buffer_handle.read(cx); let range_start = range.start; let range_end = range.end; - let buffer_id = buffer.remote_id().into(); - let lsp_request = InlayHints { range }; + let buffer_id = buffer.read(cx).remote_id().into(); + let request = InlayHints { range }; if let Some((client, project_id)) = self.upstream_client() { - let request = proto::InlayHints { + if !self.is_capable_for_proto_request(&buffer, &request, cx) { + return Task::ready(Ok(Vec::new())); + } + let proto_request = proto::InlayHints { project_id, buffer_id, start: Some(serialize_anchor(&range_start)), end: Some(serialize_anchor(&range_end)), - version: serialize_version(&buffer_handle.read(cx).version()), + version: serialize_version(&buffer.read(cx).version()), }; cx.spawn(async move |project, cx| { let response = client - .request(request) + .request(proto_request) .await .context("inlay hints proto request")?; LspCommand::response_from_proto( - lsp_request, + request, response, project.upgrade().context("No project")?, - buffer_handle.clone(), + buffer.clone(), cx.clone(), ) .await @@ -6542,13 +6680,13 @@ impl LspStore { }) } else { let lsp_request_task = self.request_lsp( - buffer_handle.clone(), + buffer.clone(), LanguageServerToQuery::FirstCapable, - lsp_request, + request, cx, ); cx.spawn(async move |_, cx| { - buffer_handle + buffer .update(cx, |buffer, _| { buffer.wait_for_edits(vec![range_start.timestamp, range_end.timestamp]) })? @@ -6772,6 +6910,11 @@ impl LspStore { cx: &mut Context, ) -> Task>>> { if let Some((client, project_id)) = self.upstream_client() { + let request = GetDocumentColor {}; + if !self.is_capable_for_proto_request(buffer, &request, cx) { + return Task::ready(Ok(HashMap::default())); + } + let request_task = client.request(proto::MultiLspQuery { project_id, buffer_id: buffer.read(cx).remote_id().to_proto(), @@ -6780,7 +6923,7 @@ impl LspStore { proto::AllLanguageServers {}, )), request: Some(proto::multi_lsp_query::Request::GetDocumentColor( - GetDocumentColor {}.to_proto(project_id, buffer.read(cx)), + request.to_proto(project_id, buffer.read(cx)), )), }); let buffer = buffer.clone(); @@ -6808,7 +6951,7 @@ impl LspStore { } }) .map(|(server_id, color_response)| { - let response = GetDocumentColor {}.response_from_proto( + let response = request.response_from_proto( color_response, project.clone(), buffer.clone(), @@ -6855,6 +6998,10 @@ impl LspStore { let position = position.to_point_utf16(buffer.read(cx)); if let Some((client, upstream_project_id)) = self.upstream_client() { + let request = GetSignatureHelp { position }; + if !self.is_capable_for_proto_request(buffer, &request, cx) { + return Task::ready(Vec::new()); + } let request_task = client.request(proto::MultiLspQuery { buffer_id: buffer.read(cx).remote_id().into(), version: serialize_version(&buffer.read(cx).version()), @@ -6863,7 +7010,7 @@ impl LspStore { proto::AllLanguageServers {}, )), request: Some(proto::multi_lsp_query::Request::GetSignatureHelp( - GetSignatureHelp { position }.to_proto(upstream_project_id, buffer.read(cx)), + request.to_proto(upstream_project_id, buffer.read(cx)), )), }); let buffer = buffer.clone(); @@ -6926,6 +7073,10 @@ impl LspStore { cx: &mut Context, ) -> Task> { if let Some((client, upstream_project_id)) = self.upstream_client() { + let request = GetHover { position }; + if !self.is_capable_for_proto_request(buffer, &request, cx) { + return Task::ready(Vec::new()); + } let request_task = client.request(proto::MultiLspQuery { buffer_id: buffer.read(cx).remote_id().into(), version: serialize_version(&buffer.read(cx).version()), @@ -6934,7 +7085,7 @@ impl LspStore { proto::AllLanguageServers {}, )), request: Some(proto::multi_lsp_query::Request::GetHover( - GetHover { position }.to_proto(upstream_project_id, buffer.read(cx)), + request.to_proto(upstream_project_id, buffer.read(cx)), )), }); let buffer = buffer.clone(); @@ -7539,16 +7690,20 @@ impl LspStore { self.downstream_client = Some((downstream_client.clone(), project_id)); for (server_id, status) in &self.language_server_statuses { - downstream_client - .send(proto::StartLanguageServer { - project_id, - server: Some(proto::LanguageServer { - id: server_id.0 as u64, - name: status.name.clone(), - worktree_id: None, - }), - }) - .log_err(); + if let Some(server) = self.language_server_for_id(*server_id) { + downstream_client + .send(proto::StartLanguageServer { + project_id, + server: Some(proto::LanguageServer { + id: server_id.to_proto(), + name: status.name.to_string(), + worktree_id: None, + }), + capabilities: serde_json::to_string(&server.capabilities()) + .expect("serializing server LSP capabilities"), + }) + .log_err(); + } } } @@ -7575,7 +7730,7 @@ impl LspStore { ( LanguageServerId(server.id as usize), LanguageServerStatus { - name: server.name, + name: LanguageServerName::from_proto(server.name), pending_work: Default::default(), has_pending_diagnostic_updates: false, progress_tokens: Default::default(), @@ -8724,18 +8879,29 @@ impl LspStore { } async fn handle_start_language_server( - this: Entity, + lsp_store: Entity, envelope: TypedEnvelope, mut cx: AsyncApp, ) -> Result<()> { let server = envelope.payload.server.context("invalid server")?; - - this.update(&mut cx, |this, cx| { + let server_capabilities = + serde_json::from_str::(&envelope.payload.capabilities) + .with_context(|| { + format!( + "incorrect server capabilities {}", + envelope.payload.capabilities + ) + })?; + lsp_store.update(&mut cx, |lsp_store, cx| { let server_id = LanguageServerId(server.id as usize); - this.language_server_statuses.insert( + let server_name = LanguageServerName::from_proto(server.name.clone()); + lsp_store + .lsp_server_capabilities + .insert(server_id, server_capabilities); + lsp_store.language_server_statuses.insert( server_id, LanguageServerStatus { - name: server.name.clone(), + name: server_name.clone(), pending_work: Default::default(), has_pending_diagnostic_updates: false, progress_tokens: Default::default(), @@ -8743,7 +8909,7 @@ impl LspStore { ); cx.emit(LspStoreEvent::LanguageServerAdded( server_id, - LanguageServerName(server.name.into()), + server_name, server.worktree_id.map(WorktreeId::from_proto), )); cx.notify(); @@ -8804,7 +8970,8 @@ impl LspStore { } non_lsp @ proto::update_language_server::Variant::StatusUpdate(_) - | non_lsp @ proto::update_language_server::Variant::RegisteredForBuffer(_) => { + | non_lsp @ proto::update_language_server::Variant::RegisteredForBuffer(_) + | non_lsp @ proto::update_language_server::Variant::MetadataUpdated(_) => { cx.emit(LspStoreEvent::LanguageServerUpdate { language_server_id, name: envelope @@ -10251,7 +10418,7 @@ impl LspStore { let name = self .language_server_statuses .remove(&server_id) - .map(|status| LanguageServerName::from(status.name.as_str())) + .map(|status| status.name.clone()) .or_else(|| { if let Some(LanguageServerState::Running { adapter, .. }) = server_state.as_ref() { Some(adapter.name()) @@ -10744,7 +10911,7 @@ impl LspStore { self.language_server_statuses.insert( server_id, LanguageServerStatus { - name: language_server.name().to_string(), + name: language_server.name(), pending_work: Default::default(), has_pending_diagnostic_updates: false, progress_tokens: Default::default(), @@ -10758,18 +10925,23 @@ impl LspStore { )); cx.emit(LspStoreEvent::RefreshInlayHints); + let server_capabilities = language_server.capabilities(); if let Some((downstream_client, project_id)) = self.downstream_client.as_ref() { downstream_client .send(proto::StartLanguageServer { project_id: *project_id, server: Some(proto::LanguageServer { - id: server_id.0 as u64, + id: server_id.to_proto(), name: language_server.name().to_string(), worktree_id: Some(key.0.to_proto()), }), + capabilities: serde_json::to_string(&server_capabilities) + .expect("serializing server LSP capabilities"), }) .log_err(); } + self.lsp_server_capabilities + .insert(server_id, server_capabilities); // Tell the language server about every open buffer in the worktree that matches the language. // Also check for buffers in worktrees that reused this server @@ -10817,10 +10989,11 @@ impl LspStore { let local = self.as_local_mut().unwrap(); - if local.registered_buffers.contains_key(&buffer.remote_id()) { + let buffer_id = buffer.remote_id(); + if local.registered_buffers.contains_key(&buffer_id) { let versions = local .buffer_snapshots - .entry(buffer.remote_id()) + .entry(buffer_id) .or_default() .entry(server_id) .and_modify(|_| { @@ -10846,10 +11019,10 @@ impl LspStore { version, initial_snapshot.text(), ); - buffer_paths_registered.push(file.abs_path(cx)); + buffer_paths_registered.push((buffer_id, file.abs_path(cx))); local .buffers_opened_in_servers - .entry(buffer.remote_id()) + .entry(buffer_id) .or_default() .insert(server_id); } @@ -10873,13 +11046,14 @@ impl LspStore { } }); - for abs_path in buffer_paths_registered { + for (buffer_id, abs_path) in buffer_paths_registered { cx.emit(LspStoreEvent::LanguageServerUpdate { language_server_id: server_id, name: Some(adapter.name()), message: proto::update_language_server::Variant::RegisteredForBuffer( proto::RegisteredForBuffer { buffer_abs_path: abs_path.to_string_lossy().to_string(), + buffer_id: buffer_id.to_proto(), }, ), }); @@ -11337,6 +11511,7 @@ impl LspStore { } fn cleanup_lsp_data(&mut self, for_server: LanguageServerId) { + self.lsp_server_capabilities.remove(&for_server); for buffer_colors in self.lsp_document_colors.values_mut() { buffer_colors.colors.remove(&for_server); buffer_colors.cache_version += 1; diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 5000ba93be..398e8bde87 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -277,6 +277,13 @@ pub enum Event { LanguageServerAdded(LanguageServerId, LanguageServerName, Option), LanguageServerRemoved(LanguageServerId), LanguageServerLog(LanguageServerId, LanguageServerLogType, String), + // [`lsp::notification::DidOpenTextDocument`] was sent to this server using the buffer data. + // Zed's buffer-related data is updated accordingly. + LanguageServerBufferRegistered { + server_id: LanguageServerId, + buffer_id: BufferId, + buffer_abs_path: PathBuf, + }, Toast { notification_id: SharedString, message: String, @@ -2931,8 +2938,8 @@ impl Project { } LspStoreEvent::LanguageServerUpdate { language_server_id, - message, name, + message, } => { if self.is_local() { self.enqueue_buffer_ordered_message( @@ -2944,6 +2951,32 @@ impl Project { ) .ok(); } + + match message { + proto::update_language_server::Variant::MetadataUpdated(update) => { + if let Some(capabilities) = update + .capabilities + .as_ref() + .and_then(|capabilities| serde_json::from_str(capabilities).ok()) + { + self.lsp_store.update(cx, |lsp_store, _| { + lsp_store + .lsp_server_capabilities + .insert(*language_server_id, capabilities); + }); + } + } + proto::update_language_server::Variant::RegisteredForBuffer(update) => { + if let Some(buffer_id) = BufferId::new(update.buffer_id).ok() { + cx.emit(Event::LanguageServerBufferRegistered { + buffer_id, + server_id: *language_server_id, + buffer_abs_path: PathBuf::from(&update.buffer_abs_path), + }); + } + } + _ => (), + } } LspStoreEvent::Notification(message) => cx.emit(Event::Toast { notification_id: "lsp".into(), @@ -3476,20 +3509,6 @@ impl Project { }) } - fn document_highlights_impl( - &mut self, - buffer: &Entity, - position: PointUtf16, - cx: &mut Context, - ) -> Task>> { - self.request_lsp( - buffer.clone(), - LanguageServerToQuery::FirstCapable, - GetDocumentHighlights { position }, - cx, - ) - } - pub fn document_highlights( &mut self, buffer: &Entity, @@ -3497,7 +3516,12 @@ impl Project { cx: &mut Context, ) -> Task>> { let position = position.to_point_utf16(buffer.read(cx)); - self.document_highlights_impl(buffer, position, cx) + self.request_lsp( + buffer.clone(), + LanguageServerToQuery::FirstCapable, + GetDocumentHighlights { position }, + cx, + ) } pub fn document_symbols( @@ -3598,14 +3622,14 @@ impl Project { .update(cx, |lsp_store, cx| lsp_store.hover(buffer, position, cx)) } - pub fn linked_edit( + pub fn linked_edits( &self, buffer: &Entity, position: Anchor, cx: &mut Context, ) -> Task>>> { self.lsp_store.update(cx, |lsp_store, cx| { - lsp_store.linked_edit(buffer, position, cx) + lsp_store.linked_edits(buffer, position, cx) }) } @@ -3697,19 +3721,6 @@ impl Project { }) } - fn prepare_rename_impl( - &mut self, - buffer: Entity, - position: PointUtf16, - cx: &mut Context, - ) -> Task> { - self.request_lsp( - buffer, - LanguageServerToQuery::FirstCapable, - PrepareRename { position }, - cx, - ) - } pub fn prepare_rename( &mut self, buffer: Entity, @@ -3717,7 +3728,12 @@ impl Project { cx: &mut Context, ) -> Task> { let position = position.to_point_utf16(buffer.read(cx)); - self.prepare_rename_impl(buffer, position, cx) + self.request_lsp( + buffer, + LanguageServerToQuery::FirstCapable, + PrepareRename { position }, + cx, + ) } pub fn perform_rename( diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 779cf95add..75ebc8339a 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -1100,7 +1100,7 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon let fake_server = fake_servers.next().await.unwrap(); let (server_id, server_name) = lsp_store.read_with(cx, |lsp_store, _| { let (id, status) = lsp_store.language_server_statuses().next().unwrap(); - (id, LanguageServerName::from(status.name.as_str())) + (id, status.name.clone()) }); // Simulate jumping to a definition in a dependency outside of the worktree. @@ -1698,7 +1698,7 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC name: "the-language-server", disk_based_diagnostics_sources: vec!["disk".into()], disk_based_diagnostics_progress_token: Some(progress_token.into()), - ..Default::default() + ..FakeLspAdapter::default() }, ); @@ -1710,6 +1710,7 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC }) .await .unwrap(); + let buffer_id = buffer.read_with(cx, |buffer, _| buffer.remote_id()); // Simulate diagnostics starting to update. let fake_server = fake_servers.next().await.unwrap(); fake_server.start_progress(progress_token).await; @@ -1736,6 +1737,14 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC ); assert_eq!(events.next().await.unwrap(), Event::RefreshInlayHints); fake_server.start_progress(progress_token).await; + assert_eq!( + events.next().await.unwrap(), + Event::LanguageServerBufferRegistered { + server_id: LanguageServerId(1), + buffer_id, + buffer_abs_path: PathBuf::from(path!("/dir/a.rs")), + } + ); assert_eq!( events.next().await.unwrap(), Event::DiskBasedDiagnosticsStarted { diff --git a/crates/proto/proto/call.proto b/crates/proto/proto/call.proto index 5212f3b43f..b5c882db56 100644 --- a/crates/proto/proto/call.proto +++ b/crates/proto/proto/call.proto @@ -71,6 +71,7 @@ message RejoinedProject { repeated WorktreeMetadata worktrees = 2; repeated Collaborator collaborators = 3; repeated LanguageServer language_servers = 4; + repeated string language_server_capabilities = 5; } message LeaveRoom {} @@ -199,6 +200,7 @@ message JoinProjectResponse { repeated WorktreeMetadata worktrees = 2; repeated Collaborator collaborators = 3; repeated LanguageServer language_servers = 4; + repeated string language_server_capabilities = 8; ChannelRole role = 6; reserved 7; } diff --git a/crates/proto/proto/lsp.proto b/crates/proto/proto/lsp.proto index e3c2f69c0b..1e693dfdf3 100644 --- a/crates/proto/proto/lsp.proto +++ b/crates/proto/proto/lsp.proto @@ -518,6 +518,7 @@ message LanguageServer { message StartLanguageServer { uint64 project_id = 1; LanguageServer server = 2; + string capabilities = 3; } message UpdateDiagnosticSummary { @@ -545,6 +546,7 @@ message UpdateLanguageServer { LspDiskBasedDiagnosticsUpdated disk_based_diagnostics_updated = 7; StatusUpdate status_update = 9; RegisteredForBuffer registered_for_buffer = 10; + ServerMetadataUpdated metadata_updated = 11; } } @@ -597,6 +599,11 @@ enum ServerBinaryStatus { message RegisteredForBuffer { string buffer_abs_path = 1; + uint64 buffer_id = 2; +} + +message ServerMetadataUpdated { + optional string capabilities = 1; } message LanguageServerLog {