diff --git a/crates/language/src/language_registry.rs b/crates/language/src/language_registry.rs index 2ac6ef456d2ee17c8710ec1c37f22ff34a648e4d..8c115af27d844e07172136d80a24abf9eeede452 100644 --- a/crates/language/src/language_registry.rs +++ b/crates/language/src/language_registry.rs @@ -319,6 +319,8 @@ impl LanguageRegistry { state .all_lsp_adapters .insert(cached.name.clone(), cached.clone()); + state.version += 1; + *state.subscription.0.borrow_mut() = (); } /// Register a fake language server and adapter @@ -354,6 +356,8 @@ impl LanguageRegistry { state .all_lsp_adapters .insert(cached_adapter.name(), cached_adapter); + state.version += 1; + *state.subscription.0.borrow_mut() = (); } /// Register a fake language server (without the adapter) diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 1488691afa9e91132d62bc378fbacdf6883cbf00..68e6265a7129f790918b1fea9251f9d709f1b5d7 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -4747,6 +4747,7 @@ impl LspStore { this.update(cx, |this, cx| { let mut plain_text_buffers = Vec::new(); + let mut buffers_with_language = Vec::new(); let mut buffers_with_unknown_injections = Vec::new(); for handle in this.buffer_store.read(cx).buffers() { let buffer = handle.read(cx); @@ -4754,8 +4755,11 @@ impl LspStore { || buffer.language() == Some(&*language::PLAIN_TEXT) { plain_text_buffers.push(handle); - } else if buffer.contains_unknown_injections() { - buffers_with_unknown_injections.push(handle); + } else { + if buffer.contains_unknown_injections() { + buffers_with_unknown_injections.push(handle.clone()); + } + buffers_with_language.push(handle); } } @@ -4785,6 +4789,24 @@ impl LspStore { } } + // Also register buffers that already have a language with + // any newly-available language servers (e.g., from extensions + // that finished loading after buffers were restored). + if let Some(local) = this.as_local_mut() { + for buffer in buffers_with_language { + if local + .registered_buffers + .contains_key(&buffer.read(cx).remote_id()) + { + local.register_buffer_with_language_servers( + &buffer, + HashSet::default(), + cx, + ); + } + } + } + for buffer in buffers_with_unknown_injections { buffer.update(cx, |buffer, cx| buffer.reparse(cx, false)); } diff --git a/crates/project/tests/integration/project_tests.rs b/crates/project/tests/integration/project_tests.rs index 4d6899189e76c0083a4501570270e44b825329bd..575c021c3db5c0a8ce1984376e0f00a355977468 100644 --- a/crates/project/tests/integration/project_tests.rs +++ b/crates/project/tests/integration/project_tests.rs @@ -1915,6 +1915,84 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) { ); } +#[gpui::test] +async fn test_late_lsp_adapter_registration(cx: &mut gpui::TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(cx.executor()); + fs.insert_tree( + path!("/dir"), + json!({ + "test.rs": "const A: i32 = 1;", + }), + ) + .await; + + let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await; + let language_registry = project.read_with(cx, |project, _| project.languages().clone()); + + // Add the language first so the buffer gets assigned a language. + language_registry.add(rust_lang()); + cx.executor().run_until_parked(); + + // Open a buffer — it gets assigned the Rust language but there is no LSP adapter yet. + let (rust_buffer, _handle) = project + .update(cx, |project, cx| { + project.open_local_buffer_with_lsp(path!("/dir/test.rs"), cx) + }) + .await + .unwrap(); + + rust_buffer.update(cx, |buffer, _| { + assert_eq!(buffer.language().map(|l| l.name()), Some("Rust".into())); + }); + + // Now register the LSP adapter late (simulating an extension loading after startup). + let mut fake_rust_servers = language_registry.register_fake_lsp( + "Rust", + FakeLspAdapter { + name: "the-rust-language-server", + capabilities: lsp::ServerCapabilities { + completion_provider: Some(lsp::CompletionOptions { + trigger_characters: Some(vec![".".to_string(), "::".to_string()]), + ..Default::default() + }), + ..Default::default() + }, + ..Default::default() + }, + ); + cx.executor().run_until_parked(); + + // The language server should start and receive a DidOpenTextDocument notification + // for the already-open buffer. + let mut fake_rust_server = fake_rust_servers.next().await.unwrap(); + assert_eq!( + fake_rust_server + .receive_notification::() + .await + .text_document, + lsp::TextDocumentItem { + uri: lsp::Uri::from_file_path(path!("/dir/test.rs")).unwrap(), + version: 0, + text: "const A: i32 = 1;".to_string(), + language_id: "rust".to_string(), + } + ); + + // The buffer should be configured with the language server's capabilities. + rust_buffer.update(cx, |buffer, _| { + assert_eq!( + buffer + .completion_triggers() + .iter() + .cloned() + .collect::>(), + &[".".to_string(), "::".to_string()] + ); + }); +} + #[gpui::test] async fn test_language_server_relative_path(cx: &mut gpui::TestAppContext) { init_test(cx);