Actually check and reinstall broken server

Julia created

Change summary

crates/language/src/language.rs | 66 ++++++-----------------
crates/project/src/project.rs   | 98 ++++++++++++++++++++++++++++++++++
crates/zed/src/languages/c.rs   |  4 +
3 files changed, 118 insertions(+), 50 deletions(-)

Detailed changes

crates/language/src/language.rs 🔗

@@ -20,7 +20,7 @@ use futures::{
 use gpui::{executor::Background, AppContext, Task};
 use highlight_map::HighlightMap;
 use lazy_static::lazy_static;
-use lsp::{CodeActionKind, LanguageServer, LanguageServerBinaries, LanguageServerBinary};
+use lsp::{CodeActionKind, LanguageServerBinaries, LanguageServerBinary};
 use parking_lot::{Mutex, RwLock};
 use postage::watch;
 use regex::Regex;
@@ -35,13 +35,11 @@ use std::{
     mem,
     ops::{Not, Range},
     path::{Path, PathBuf},
-    process::Stdio,
     str,
     sync::{
         atomic::{AtomicUsize, Ordering::SeqCst},
         Arc,
     },
-    time::Duration,
 };
 use syntax_map::SyntaxSnapshot;
 use theme::{SyntaxTheme, Theme};
@@ -860,7 +858,7 @@ impl LanguageRegistry {
         let download_dir = self
             .language_server_download_dir
             .clone()
-            .ok_or_else(|| anyhow!("language server download directory has not been assigned"))
+            .ok_or_else(|| anyhow!("language server download directory has not been assigned before starting server"))
             .log_err()?;
         let this = self.clone();
         let language = language.clone();
@@ -913,54 +911,26 @@ impl LanguageRegistry {
         self.lsp_binary_statuses_rx.clone()
     }
 
-    pub async fn check_errored_lsp_installation(
+    pub fn delete_server_container(
         &self,
-        language_server: Arc<LanguageServer>,
+        adapter: Arc<CachedLspAdapter>,
         cx: &mut AppContext,
-    ) {
-        // Check if child process is running
-        if !language_server.is_dead() {
-            return;
-        }
-
-        // If not, get check binary
-        let test_binary = match language_server.test_installation_binary() {
-            Some(test_binary) => test_binary.clone(),
-            None => return,
-        };
-
-        // Run
-        const PROCESS_TIMEOUT: Duration = Duration::from_secs(5);
-        let mut timeout = cx.background().timer(PROCESS_TIMEOUT).fuse();
-
-        let mut errored = false;
-        let result = smol::process::Command::new(&test_binary.path)
-            .current_dir(&test_binary.path)
-            .args(test_binary.arguments)
-            .stdin(Stdio::piped())
-            .stdout(Stdio::piped())
-            .stderr(Stdio::inherit())
-            .kill_on_drop(true)
-            .spawn();
-
-        if let Ok(mut process) = result {
-            futures::select! {
-                _ = process.status().fuse() => {}
-                _ = timeout => errored = true,
-            }
-        } else {
-            errored = true;
-        }
-
-        dbg!(errored);
+    ) -> Task<()> {
+        let mut lock = self.lsp_binary_paths.lock();
+        lock.remove(&adapter.name);
 
-        // If failure clear container dir
-
-        // Prompt binary retrieval
+        let download_dir = self
+            .language_server_download_dir
+            .clone()
+            .expect("language server download directory has not been assigned before deleting server container");
 
-        // Start language server
- 
-        // Update project server state
+        cx.spawn(|_| async move {
+            let container_dir = download_dir.join(adapter.name.0.as_ref());
+            smol::fs::remove_dir_all(container_dir)
+                .await
+                .context("server container removal")
+                .log_err();
+        })
     }
 }
 

crates/project/src/project.rs 🔗

@@ -2461,6 +2461,54 @@ impl Project {
         }
     }
 
+    fn reinstall_language_server(
+        &mut self,
+        server_id: LanguageServerId,
+        cx: &mut ModelContext<Self>,
+    ) -> Option<Task<()>> {
+        let (adapter, language, server) = match self.language_servers.remove(&server_id) {
+            Some(LanguageServerState::Running {
+                adapter,
+                language,
+                server,
+                ..
+            }) => (adapter.clone(), language.clone(), server),
+
+            _ => return None,
+        };
+
+        Some(cx.spawn(move |this, mut cx| async move {
+            if let Some(task) = server.shutdown() {
+                task.await;
+            }
+
+            // TODO: This is race-safe with regards to preventing new instances from
+            // starting while deleting, but existing instances in other projects are going
+            // to be very confused and messed up
+            this.update(&mut cx, |this, cx| {
+                this.languages.delete_server_container(adapter.clone(), cx)
+            })
+            .await;
+
+            this.update(&mut cx, |this, mut cx| {
+                for worktree in &this.worktrees {
+                    let root_path = match worktree.upgrade(cx) {
+                        Some(worktree) => worktree.read(cx).abs_path(),
+                        None => continue,
+                    };
+
+                    this.languages.start_language_server(
+                        language.clone(),
+                        adapter.clone(),
+                        root_path,
+                        this.client.http_client(),
+                        &mut cx,
+                    );
+                }
+            })
+        }))
+    }
+
     async fn setup_and_insert_language_server(
         this: WeakModelHandle<Self>,
         initialization_options: Option<serde_json::Value>,
@@ -2950,8 +2998,54 @@ impl Project {
         language_server: Arc<LanguageServer>,
         cx: &mut ModelContext<Self>,
     ) {
-        self.languages
-            .check_errored_lsp_installation(language_server, cx);
+        cx.spawn(|this, mut cx| async move {
+            if !language_server.is_dead() {
+                return;
+            }
+
+            let server_id = language_server.server_id();
+            let test_binary = match language_server.test_installation_binary() {
+                Some(test_binary) => test_binary.clone(),
+                None => return,
+            };
+
+            const PROCESS_TIMEOUT: Duration = Duration::from_secs(5);
+            let mut timeout = cx.background().timer(PROCESS_TIMEOUT).fuse();
+
+            let mut errored = false;
+            let result = smol::process::Command::new(&test_binary.path)
+                .current_dir(&test_binary.path)
+                .args(test_binary.arguments)
+                .stdin(Stdio::piped())
+                .stdout(Stdio::piped())
+                .stderr(Stdio::inherit())
+                .kill_on_drop(true)
+                .spawn();
+
+            if let Ok(mut process) = result {
+                futures::select! {
+                    status = process.status().fuse() => match status {
+                        Ok(status) => errored = !status.success(),
+                        Err(_) => errored = true,
+                    },
+
+                    _ = timeout => {}
+                }
+            } else {
+                errored = true;
+            }
+
+            if errored {
+                let task = this.update(&mut cx, move |this, mut cx| {
+                    this.reinstall_language_server(server_id, &mut cx)
+                });
+
+                if let Some(task) = task {
+                    task.await;
+                }
+            }
+        })
+        .detach();
     }
 
     fn on_lsp_progress(

crates/zed/src/languages/c.rs 🔗

@@ -109,6 +109,10 @@ impl super::LspAdapter for CLspAdapter {
         .await
         .log_err()
     }
+    
+    fn installation_test_binary(&self, container_dir: PathBuf) -> LanguageServerBinary {
+        unimplemented!();
+    }
 
     async fn label_for_completion(
         &self,