eval: Improve lang server idle detection (#29135)

Agus Zubiaga created

Brings back #29013 after it was accidentally reverted by
https://github.com/zed-industries/zed/pull/28961/commits/e9bb15b9063615762c866c30aaf646acb12af1f3.

Release Notes:

- N/A

Change summary

crates/eval/src/example.rs | 119 ++++++++++++++++++---------------------
1 file changed, 56 insertions(+), 63 deletions(-)

Detailed changes

crates/eval/src/example.rs 🔗

@@ -8,12 +8,12 @@ use futures::channel::mpsc;
 use futures::{FutureExt, StreamExt as _, select_biased};
 use gpui::{App, AppContext as _, AsyncApp, Entity, Task};
 use handlebars::Handlebars;
-use language::{DiagnosticSeverity, OffsetRangeExt};
+use language::{Buffer, DiagnosticSeverity, OffsetRangeExt};
 use language_model::{
     LanguageModel, LanguageModelCompletionEvent, LanguageModelRequest, LanguageModelRequestMessage,
     MessageContent, Role, StopReason, TokenUsage,
 };
-use project::{LspStore, Project, ProjectPath};
+use project::{Project, ProjectPath};
 use serde::{Deserialize, Serialize};
 use std::cell::RefCell;
 use std::fmt::Write as _;
@@ -271,7 +271,7 @@ impl Example {
                 })?
                 .await;
 
-            let lsp_open_handle_and_store = if this.base.require_lsp {
+            let lsp = if this.base.require_lsp {
                 let language_extension = this.base.language_extension.as_deref().context(
                     "language_extension field is required in base.toml when `require_lsp == true`",
                 )?;
@@ -301,39 +301,13 @@ impl Example {
 
                 let language_file_buffer = open_language_file_buffer_task.await?;
 
-                let (lsp_open_handle, lsp_store) = project.update(cx, |project, cx| {
-                    (
-                        project.register_buffer_with_language_servers(&language_file_buffer, cx),
-                        project.lsp_store().clone(),
-                    )
+                let lsp_open_handle = project.update(cx, |project, cx| {
+                    project.register_buffer_with_language_servers(&language_file_buffer, cx)
                 })?;
 
-                // TODO: remove this once the diagnostics tool waits for new diagnostics
-                cx.background_executor().timer(Duration::new(5, 0)).await;
-                wait_for_lang_server(&lsp_store, this.log_prefix.clone(), cx).await?;
-
-                lsp_store.update(cx, |lsp_store, cx| {
-                    lsp_open_handle.update(cx, |buffer, cx| {
-                        buffer.update(cx, |buffer, cx| {
-                            let has_language_server = lsp_store
-                                .language_servers_for_local_buffer(buffer, cx)
-                                .next()
-                                .is_some();
-                            if has_language_server {
-                                Ok(())
-                            } else {
-                                Err(anyhow!(
-                                    "`{:?}` was opened to cause the language server to start, \
-                                    but no language servers are registered for its buffer. \
-                                    Set `require_lsp = false` in `base.toml` to skip this.",
-                                    language_file
-                                ))
-                            }
-                        })
-                    })
-                })??;
+                wait_for_lang_server(&project, &language_file_buffer, this.log_prefix.clone(), cx).await?;
 
-                Some((lsp_open_handle, lsp_store))
+                Some((lsp_open_handle, language_file_buffer))
             } else {
                 None
             };
@@ -479,8 +453,8 @@ impl Example {
 
             println!("{}Stopped", this.log_prefix);
 
-            if let Some((_, lsp_store)) = lsp_open_handle_and_store.as_ref() {
-                wait_for_lang_server(lsp_store, this.log_prefix.clone(), cx).await?;
+            if let Some((_, language_file_buffer)) = lsp.as_ref() {
+                wait_for_lang_server(&project, &language_file_buffer, this.log_prefix.clone(), cx).await?;
             }
 
             println!("{}Getting repository diff", this.log_prefix);
@@ -504,7 +478,7 @@ impl Example {
             };
 
             drop(subscription);
-            drop(lsp_open_handle_and_store);
+            drop(lsp);
 
             if let Some(diagnostics_before) = &diagnostics_before {
                 fs::write(example_output_dir.join("diagnostics_before.txt"), diagnostics_before)?;
@@ -674,27 +648,42 @@ impl Example {
 }
 
 fn wait_for_lang_server(
-    lsp_store: &Entity<LspStore>,
+    project: &Entity<Project>,
+    buffer: &Entity<Buffer>,
     log_prefix: String,
     cx: &mut AsyncApp,
 ) -> Task<Result<()>> {
-    if cx
-        .update(|cx| !has_pending_lang_server_work(lsp_store, cx))
-        .unwrap()
-        || std::env::var("ZED_EVAL_SKIP_LS_WAIT").is_ok()
-    {
-        return Task::ready(anyhow::Ok(()));
-    }
-
     println!("{}⏵ Waiting for language server", log_prefix);
 
     let (mut tx, mut rx) = mpsc::channel(1);
 
-    let subscription =
-        cx.subscribe(&lsp_store, {
-            let log_prefix = log_prefix.clone();
-            move |lsp_store, event, cx| {
-                match event {
+    let lsp_store = project
+        .update(cx, |project, _| project.lsp_store())
+        .unwrap();
+
+    let has_lang_server = buffer
+        .update(cx, |buffer, cx| {
+            lsp_store.update(cx, |lsp_store, cx| {
+                lsp_store
+                    .language_servers_for_local_buffer(&buffer, cx)
+                    .next()
+                    .is_some()
+            })
+        })
+        .unwrap_or(false);
+
+    if has_lang_server {
+        project
+            .update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))
+            .unwrap()
+            .detach();
+    }
+
+    let subscriptions =
+        [
+            cx.subscribe(&lsp_store, {
+                let log_prefix = log_prefix.clone();
+                move |_, event, _| match event {
                     project::LspStoreEvent::LanguageServerUpdate {
                         message:
                             client::proto::update_language_server::Variant::WorkProgress(
@@ -707,12 +696,23 @@ fn wait_for_lang_server(
                     } => println!("{}⟲ {message}", log_prefix),
                     _ => {}
                 }
-
-                if !has_pending_lang_server_work(&lsp_store, cx) {
-                    tx.try_send(()).ok();
+            }),
+            cx.subscribe(&project, {
+                let buffer = buffer.clone();
+                move |project, event, cx| match event {
+                    project::Event::LanguageServerAdded(_, _, _) => {
+                        let buffer = buffer.clone();
+                        project
+                            .update(cx, |project, cx| project.save_buffer(buffer, cx))
+                            .detach();
+                    }
+                    project::Event::DiskBasedDiagnosticsFinished { .. } => {
+                        tx.try_send(()).ok();
+                    }
+                    _ => {}
                 }
-            }
-        });
+            }),
+        ];
 
     cx.spawn(async move |cx| {
         let timeout = cx.background_executor().timer(Duration::new(60 * 5, 0));
@@ -725,18 +725,11 @@ fn wait_for_lang_server(
                 Err(anyhow!("LSP wait timed out after 5 minutes"))
             }
         };
-        drop(subscription);
+        drop(subscriptions);
         result
     })
 }
 
-fn has_pending_lang_server_work(lsp_store: &Entity<LspStore>, cx: &App) -> bool {
-    lsp_store
-        .read(cx)
-        .language_server_statuses()
-        .any(|(_, status)| !status.pending_work.is_empty())
-}
-
 async fn query_lsp_diagnostics(
     project: Entity<Project>,
     cx: &mut AsyncApp,