@@ -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)
@@ -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));
}
@@ -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::<lsp::notification::DidOpenTextDocument>()
+ .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::<Vec<_>>(),
+ &[".".to_string(), "::".to_string()]
+ );
+ });
+}
+
#[gpui::test]
async fn test_language_server_relative_path(cx: &mut gpui::TestAppContext) {
init_test(cx);