Many steps toward validating and reinstalling server after failure

Julia created

Change summary

crates/activity_indicator/src/activity_indicator.rs |  17 
crates/copilot/src/copilot.rs                       |  15 
crates/language/src/language.rs                     |  99 ++++
crates/lsp/src/lsp.rs                               |  51 ++
crates/project/src/project.rs                       | 275 +++++++++-----
crates/util/src/util.rs                             |  14 
crates/zed/src/languages/c.rs                       |   1 
crates/zed/src/languages/elixir.rs                  |   2 
crates/zed/src/languages/go.rs                      |   1 
crates/zed/src/languages/html.rs                    |   3 
crates/zed/src/languages/json.rs                    |   3 
crates/zed/src/languages/language_plugin.rs         |   3 
crates/zed/src/languages/lua.rs                     |   3 
crates/zed/src/languages/python.rs                  |   3 
crates/zed/src/languages/ruby.rs                    |   3 
crates/zed/src/languages/rust.rs                    |   1 
crates/zed/src/languages/typescript.rs              |   4 
crates/zed/src/languages/yaml.rs                    |   5 
styles/tsconfig.json                                |   1 
19 files changed, 348 insertions(+), 156 deletions(-)

Detailed changes

crates/activity_indicator/src/activity_indicator.rs 🔗

@@ -203,20 +203,17 @@ impl ActivityIndicator {
         }
 
         // Show any language server installation info.
+        let mut validating = SmallVec::<[_; 3]>::new();
         let mut downloading = SmallVec::<[_; 3]>::new();
         let mut checking_for_update = SmallVec::<[_; 3]>::new();
         let mut failed = SmallVec::<[_; 3]>::new();
         for status in &self.statuses {
+            let name = status.name.clone();
             match status.status {
-                LanguageServerBinaryStatus::CheckingForUpdate => {
-                    checking_for_update.push(status.name.clone());
-                }
-                LanguageServerBinaryStatus::Downloading => {
-                    downloading.push(status.name.clone());
-                }
-                LanguageServerBinaryStatus::Failed { .. } => {
-                    failed.push(status.name.clone());
-                }
+                LanguageServerBinaryStatus::Validating => validating.push(name),
+                LanguageServerBinaryStatus::CheckingForUpdate => checking_for_update.push(name),
+                LanguageServerBinaryStatus::Downloading => downloading.push(name),
+                LanguageServerBinaryStatus::Failed { .. } => failed.push(name),
                 LanguageServerBinaryStatus::Downloaded | LanguageServerBinaryStatus::Cached => {}
             }
         }
@@ -245,7 +242,7 @@ impl ActivityIndicator {
                 ),
                 on_click: None,
             };
-        } else if !failed.is_empty() {
+        } else if !failed.is_empty() || !validating.is_empty() {
             return Content {
                 icon: Some(WARNING_ICON),
                 message: format!(

crates/copilot/src/copilot.rs 🔗

@@ -15,7 +15,7 @@ use language::{
     ToPointUtf16,
 };
 use log::{debug, error};
-use lsp::{LanguageServer, LanguageServerId};
+use lsp::{LanguageServer, LanguageServerBinaries, LanguageServerBinary, LanguageServerId};
 use node_runtime::NodeRuntime;
 use request::{LogMessage, StatusNotification};
 use settings::SettingsStore;
@@ -361,11 +361,18 @@ impl Copilot {
             let start_language_server = async {
                 let server_path = get_copilot_lsp(http).await?;
                 let node_path = node_runtime.binary_path().await?;
-                let arguments: &[OsString] = &[server_path.into(), "--stdio".into()];
+                let arguments: Vec<OsString> = vec![server_path.into(), "--stdio".into()];
+                let binary = LanguageServerBinary {
+                    path: node_path,
+                    arguments,
+                };
+                let binaries = LanguageServerBinaries {
+                    binary: binary.clone(),
+                    installation_test_binary: binary,
+                };
                 let server = LanguageServer::new(
                     LanguageServerId(0),
-                    &node_path,
-                    arguments,
+                    binaries,
                     Path::new("/"),
                     None,
                     cx.clone(),

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;
+use lsp::{CodeActionKind, LanguageServer, LanguageServerBinaries, LanguageServerBinary};
 use parking_lot::{Mutex, RwLock};
 use postage::watch;
 use regex::Regex;
@@ -30,17 +30,18 @@ use std::{
     any::Any,
     borrow::Cow,
     cell::RefCell,
-    ffi::OsString,
     fmt::Debug,
     hash::Hash,
     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};
@@ -86,12 +87,6 @@ pub trait ToLspPosition {
 #[derive(Clone, Debug, PartialEq, Eq, Hash)]
 pub struct LanguageServerName(pub Arc<str>);
 
-#[derive(Debug, Clone, Deserialize)]
-pub struct LanguageServerBinary {
-    pub path: PathBuf,
-    pub arguments: Vec<OsString>,
-}
-
 /// Represents a Language Server, with certain cached sync properties.
 /// Uses [`LspAdapter`] under the hood, but calls all 'static' methods
 /// once at startup, and caches the results.
@@ -148,6 +143,10 @@ impl CachedLspAdapter {
         self.adapter.cached_server_binary(container_dir).await
     }
 
+    async fn installation_test_binary(&self, container_dir: PathBuf) -> LanguageServerBinary {
+        self.adapter.installation_test_binary(container_dir)
+    }
+
     pub fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> {
         self.adapter.code_action_kinds()
     }
@@ -205,6 +204,10 @@ pub trait LspAdapter: 'static + Send + Sync {
 
     async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<LanguageServerBinary>;
 
+    fn installation_test_binary(&self, _container_dir: PathBuf) -> LanguageServerBinary {
+        unimplemented!();
+    }
+
     async fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
 
     async fn process_completion(&self, _: &mut lsp::CompletionItem) {}
@@ -485,6 +488,7 @@ struct BracketConfig {
 
 #[derive(Clone)]
 pub enum LanguageServerBinaryStatus {
+    Validating,
     CheckingForUpdate,
     Downloading,
     Downloaded,
@@ -515,7 +519,7 @@ pub struct LanguageRegistry {
     lsp_binary_paths: Mutex<
         HashMap<
             LanguageServerName,
-            Shared<BoxFuture<'static, Result<LanguageServerBinary, Arc<anyhow::Error>>>>,
+            Shared<BoxFuture<'static, Result<LanguageServerBinaries, Arc<anyhow::Error>>>>,
         >,
     >,
     executor: Option<Arc<Background>>,
@@ -891,8 +895,7 @@ impl LanguageRegistry {
             let binary = entry.clone().map_err(|e| anyhow!(e)).await?;
             let server = lsp::LanguageServer::new(
                 server_id,
-                &binary.path,
-                &binary.arguments,
+                binary,
                 &root_path,
                 adapter.code_action_kinds(),
                 cx,
@@ -909,6 +912,56 @@ impl LanguageRegistry {
     ) -> async_broadcast::Receiver<(Arc<Language>, LanguageServerBinaryStatus)> {
         self.lsp_binary_statuses_rx.clone()
     }
+
+    pub async fn check_errored_lsp_installation(
+        &self,
+        language_server: Arc<LanguageServer>,
+        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);
+
+        // If failure clear container dir
+
+        // Prompt binary retrieval
+
+        // Start language server
+ 
+        // Update project server state
+    }
 }
 
 impl LanguageRegistryState {
@@ -961,7 +1014,7 @@ async fn get_binary(
     http_client: Arc<dyn HttpClient>,
     download_dir: Arc<Path>,
     statuses: async_broadcast::Sender<(Arc<Language>, LanguageServerBinaryStatus)>,
-) -> Result<LanguageServerBinary> {
+) -> Result<LanguageServerBinaries> {
     let container_dir = download_dir.join(adapter.name.0.as_ref());
     if !container_dir.exists() {
         smol::fs::create_dir_all(&container_dir)
@@ -979,11 +1032,15 @@ async fn get_binary(
     .await;
 
     if let Err(error) = binary.as_ref() {
-        if let Some(cached) = adapter.cached_server_binary(container_dir).await {
+        if let Some(binary) = adapter.cached_server_binary(container_dir.clone()).await {
             statuses
                 .broadcast((language.clone(), LanguageServerBinaryStatus::Cached))
                 .await?;
-            return Ok(cached);
+            let installation_test_binary = adapter.installation_test_binary(container_dir).await;
+            return Ok(LanguageServerBinaries {
+                binary,
+                installation_test_binary,
+            });
         } else {
             statuses
                 .broadcast((
@@ -995,6 +1052,7 @@ async fn get_binary(
                 .await?;
         }
     }
+
     binary
 }
 
@@ -1004,7 +1062,7 @@ async fn fetch_latest_binary(
     http_client: Arc<dyn HttpClient>,
     container_dir: &Path,
     lsp_binary_statuses_tx: async_broadcast::Sender<(Arc<Language>, LanguageServerBinaryStatus)>,
-) -> Result<LanguageServerBinary> {
+) -> Result<LanguageServerBinaries> {
     let container_dir: Arc<Path> = container_dir.into();
     lsp_binary_statuses_tx
         .broadcast((
@@ -1012,19 +1070,28 @@ async fn fetch_latest_binary(
             LanguageServerBinaryStatus::CheckingForUpdate,
         ))
         .await?;
+
     let version_info = adapter
         .fetch_latest_server_version(http_client.clone())
         .await?;
     lsp_binary_statuses_tx
         .broadcast((language.clone(), LanguageServerBinaryStatus::Downloading))
         .await?;
+
     let binary = adapter
         .fetch_server_binary(version_info, http_client, container_dir.to_path_buf())
         .await?;
+    let installation_test_binary = adapter
+        .installation_test_binary(container_dir.to_path_buf())
+        .await;
     lsp_binary_statuses_tx
         .broadcast((language.clone(), LanguageServerBinaryStatus::Downloaded))
         .await?;
-    Ok(binary)
+
+    Ok(LanguageServerBinaries {
+        binary,
+        installation_test_binary,
+    })
 }
 
 impl Language {

crates/lsp/src/lsp.rs 🔗

@@ -16,6 +16,7 @@ use smol::{
     process::{self, Child},
 };
 use std::{
+    ffi::OsString,
     fmt,
     future::Future,
     io::Write,
@@ -36,6 +37,18 @@ type NotificationHandler = Box<dyn Send + FnMut(Option<usize>, &str, AsyncAppCon
 type ResponseHandler = Box<dyn Send + FnOnce(Result<&str, Error>)>;
 type IoHandler = Box<dyn Send + FnMut(bool, &str)>;
 
+#[derive(Debug, Clone, Deserialize)]
+pub struct LanguageServerBinary {
+    pub path: PathBuf,
+    pub arguments: Vec<OsString>,
+}
+
+#[derive(Debug, Clone, Deserialize)]
+pub struct LanguageServerBinaries {
+    pub binary: LanguageServerBinary,
+    pub installation_test_binary: LanguageServerBinary,
+}
+
 pub struct LanguageServer {
     server_id: LanguageServerId,
     next_id: AtomicUsize,
@@ -51,7 +64,8 @@ pub struct LanguageServer {
     io_tasks: Mutex<Option<(Task<Option<()>>, Task<Option<()>>)>>,
     output_done_rx: Mutex<Option<barrier::Receiver>>,
     root_path: PathBuf,
-    _server: Option<Child>,
+    server: Option<Mutex<Child>>,
+    test_installation_binary: Option<LanguageServerBinary>,
 }
 
 #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
@@ -119,10 +133,9 @@ struct Error {
 }
 
 impl LanguageServer {
-    pub fn new<T: AsRef<std::ffi::OsStr>>(
+    pub fn new(
         server_id: LanguageServerId,
-        binary_path: &Path,
-        arguments: &[T],
+        binaries: LanguageServerBinaries,
         root_path: &Path,
         code_action_kinds: Option<Vec<CodeActionKind>>,
         cx: AsyncAppContext,
@@ -133,9 +146,9 @@ impl LanguageServer {
             root_path.parent().unwrap_or_else(|| Path::new("/"))
         };
 
-        let mut server = process::Command::new(binary_path)
+        let mut server = process::Command::new(&binaries.binary.path)
             .current_dir(working_dir)
-            .args(arguments)
+            .args(binaries.binary.arguments)
             .stdin(Stdio::piped())
             .stdout(Stdio::piped())
             .stderr(Stdio::inherit())
@@ -149,6 +162,7 @@ impl LanguageServer {
             stdin,
             stout,
             Some(server),
+            Some(binaries.installation_test_binary),
             root_path,
             code_action_kinds,
             cx,
@@ -164,7 +178,7 @@ impl LanguageServer {
             },
         );
 
-        if let Some(name) = binary_path.file_name() {
+        if let Some(name) = binaries.binary.path.file_name() {
             server.name = name.to_string_lossy().to_string();
         }
 
@@ -176,6 +190,7 @@ impl LanguageServer {
         stdin: Stdin,
         stdout: Stdout,
         server: Option<Child>,
+        test_installation_binary: Option<LanguageServerBinary>,
         root_path: &Path,
         code_action_kinds: Option<Vec<CodeActionKind>>,
         cx: AsyncAppContext,
@@ -229,10 +244,28 @@ impl LanguageServer {
             io_tasks: Mutex::new(Some((input_task, output_task))),
             output_done_rx: Mutex::new(Some(output_done_rx)),
             root_path: root_path.to_path_buf(),
-            _server: server,
+            server: server.map(|server| Mutex::new(server)),
+            test_installation_binary,
         }
     }
 
+    pub fn is_dead(&self) -> bool {
+        let server = match self.server.as_ref() {
+            Some(server) => server,
+            None => return false, // Fake server for tests
+        };
+
+        match server.lock().try_status() {
+            Ok(Some(_)) => true,
+            Ok(None) => false,
+            Err(_) => true,
+        }
+    }
+
+    pub fn test_installation_binary(&self) -> &Option<LanguageServerBinary> {
+        &self.test_installation_binary
+    }
+
     pub fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> {
         self.code_action_kinds.clone()
     }
@@ -813,6 +846,7 @@ impl LanguageServer {
             stdin_writer,
             stdout_reader,
             None,
+            None,
             Path::new("/"),
             None,
             cx.clone(),
@@ -824,6 +858,7 @@ impl LanguageServer {
                 stdout_writer,
                 stdin_reader,
                 None,
+                None,
                 Path::new("/"),
                 None,
                 cx,

crates/project/src/project.rs 🔗

@@ -45,7 +45,7 @@ use language::{
 use log::error;
 use lsp::{
     DiagnosticSeverity, DiagnosticTag, DidChangeWatchedFilesRegistrationOptions,
-    DocumentHighlightKind, LanguageServer, LanguageServerId,
+    DocumentHighlightKind, LanguageServer, LanguageServerId, OneOf,
 };
 use lsp_command::*;
 use postage::watch;
@@ -65,6 +65,7 @@ use std::{
     num::NonZeroU32,
     ops::Range,
     path::{Component, Path, PathBuf},
+    process::Stdio,
     rc::Rc,
     str,
     sync::{
@@ -277,6 +278,7 @@ pub enum Event {
 }
 
 pub enum LanguageServerState {
+    Validating(Task<Option<Arc<LanguageServer>>>),
     Starting(Task<Option<Arc<LanguageServer>>>),
     Running {
         language: Arc<Language>,
@@ -2447,7 +2449,7 @@ impl Project {
 
                         Err(err) => {
                             log::warn!("Error starting language server {:?}: {}", server_name, err);
-                            // TODO: Prompt installation validity check
+                            // TODO: Prompt installation validity check LSP ERROR
                             None
                         }
                     }
@@ -2831,10 +2833,8 @@ impl Project {
                 let mut root_path = None;
 
                 let server = match server_state {
-                    Some(LanguageServerState::Starting(started_language_server)) => {
-                        started_language_server.await
-                    }
-
+                    Some(LanguageServerState::Validating(task)) => task.await,
+                    Some(LanguageServerState::Starting(task)) => task.await,
                     Some(LanguageServerState::Running { server, .. }) => Some(server),
                     None => None,
                 };
@@ -2945,6 +2945,15 @@ impl Project {
         .detach();
     }
 
+    fn check_errored_lsp_installation(
+        &self,
+        language_server: Arc<LanguageServer>,
+        cx: &mut ModelContext<Self>,
+    ) {
+        self.languages
+            .check_errored_lsp_installation(language_server, cx);
+    }
+
     fn on_lsp_progress(
         &mut self,
         progress: lsp::ProgressParams,
@@ -3716,29 +3725,26 @@ impl Project {
         tab_size: NonZeroU32,
         cx: &mut AsyncAppContext,
     ) -> Result<Vec<(Range<Anchor>, String)>> {
-        let text_document =
-            lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path(abs_path).unwrap());
+        let uri = lsp::Url::from_file_path(abs_path)
+            .map_err(|_| anyhow!("failed to convert abs path to uri"))?;
+        let text_document = lsp::TextDocumentIdentifier::new(uri);
         let capabilities = &language_server.capabilities();
-        let lsp_edits = if capabilities
-            .document_formatting_provider
-            .as_ref()
-            .map_or(false, |provider| *provider != lsp::OneOf::Left(false))
-        {
+
+        let formatting_provider = capabilities.document_formatting_provider.as_ref();
+        let range_formatting_provider = capabilities.document_range_formatting_provider.as_ref();
+
+        let result = if !matches!(formatting_provider, Some(OneOf::Left(false))) {
             language_server
                 .request::<lsp::request::Formatting>(lsp::DocumentFormattingParams {
                     text_document,
                     options: lsp_command::lsp_formatting_options(tab_size.get()),
                     work_done_progress_params: Default::default(),
                 })
-                .await?
-        } else if capabilities
-            .document_range_formatting_provider
-            .as_ref()
-            .map_or(false, |provider| *provider != lsp::OneOf::Left(false))
-        {
+                .await
+        } else if !matches!(range_formatting_provider, Some(OneOf::Left(false))) {
             let buffer_start = lsp::Position::new(0, 0);
-            let buffer_end =
-                buffer.read_with(cx, |buffer, _| point_to_lsp(buffer.max_point_utf16()));
+            let buffer_end = buffer.read_with(cx, |b, _| point_to_lsp(b.max_point_utf16()));
+
             language_server
                 .request::<lsp::request::RangeFormatting>(lsp::DocumentRangeFormattingParams {
                     text_document,
@@ -3746,9 +3752,27 @@ impl Project {
                     options: lsp_command::lsp_formatting_options(tab_size.get()),
                     work_done_progress_params: Default::default(),
                 })
-                .await?
+                .await
         } else {
-            None
+            Ok(None)
+        };
+
+        let lsp_edits = match result {
+            Ok(lsp_edits) => lsp_edits,
+
+            Err(err) => {
+                log::warn!(
+                    "Error firing format request to {}: {}",
+                    language_server.name(),
+                    err
+                );
+
+                this.update(cx, |this, cx| {
+                    this.check_errored_lsp_installation(language_server.clone(), cx);
+                });
+
+                None
+            }
         };
 
         if let Some(lsp_edits) = lsp_edits {
@@ -3757,7 +3781,7 @@ impl Project {
             })
             .await
         } else {
-            Ok(Default::default())
+            Ok(Vec::new())
         }
     }
 
@@ -3865,80 +3889,89 @@ impl Project {
             let mut requests = Vec::new();
             for ((worktree_id, _), server_id) in self.language_server_ids.iter() {
                 let worktree_id = *worktree_id;
-                if let Some(worktree) = self
-                    .worktree_for_id(worktree_id, cx)
-                    .and_then(|worktree| worktree.read(cx).as_local())
-                {
-                    if let Some(LanguageServerState::Running {
+                let worktree_handle = self.worktree_for_id(worktree_id, cx);
+                let worktree = match worktree_handle.and_then(|tree| tree.read(cx).as_local()) {
+                    Some(worktree) => worktree,
+                    None => continue,
+                };
+                let worktree_abs_path = worktree.abs_path().clone();
+
+                let (adapter, language, server) = match self.language_servers.get(server_id) {
+                    Some(LanguageServerState::Running {
                         adapter,
                         language,
                         server,
                         ..
-                    }) = self.language_servers.get(server_id)
-                    {
-                        let adapter = adapter.clone();
-                        let language = language.clone();
-                        let worktree_abs_path = worktree.abs_path().clone();
-                        requests.push(
-                            server
-                                .request::<lsp::request::WorkspaceSymbolRequest>(
-                                    lsp::WorkspaceSymbolParams {
-                                        query: query.to_string(),
-                                        ..Default::default()
-                                    },
-                                )
-                                .log_err()
-                                .map(move |response| {
-                                    let lsp_symbols = response.flatten().map(|symbol_response| match symbol_response {
-                                        lsp::WorkspaceSymbolResponse::Flat(flat_responses) => {
-                                            flat_responses.into_iter().map(|lsp_symbol| {
-                                                (lsp_symbol.name, lsp_symbol.kind, lsp_symbol.location)
-                                            }).collect::<Vec<_>>()
-                                        }
-                                        lsp::WorkspaceSymbolResponse::Nested(nested_responses) => {
-                                            nested_responses.into_iter().filter_map(|lsp_symbol| {
-                                                let location = match lsp_symbol.location {
-                                                    lsp::OneOf::Left(location) => location,
-                                                    lsp::OneOf::Right(_) => {
-                                                        error!("Unexpected: client capabilities forbid symbol resolutions in workspace.symbol.resolveSupport");
-                                                        return None
-                                                    }
-                                                };
-                                                Some((lsp_symbol.name, lsp_symbol.kind, location))
-                                            }).collect::<Vec<_>>()
-                                        }
-                                    }).unwrap_or_default();
-
-                                    (
-                                        adapter,
-                                        language,
-                                        worktree_id,
-                                        worktree_abs_path,
-                                        lsp_symbols,
-                                    )
-                                }),
-                        );
-                    }
-                }
+                    }) => (adapter.clone(), language.clone(), server),
+
+                    _ => continue,
+                };
+
+                requests.push(
+                    server
+                        .request::<lsp::request::WorkspaceSymbolRequest>(
+                            lsp::WorkspaceSymbolParams {
+                                query: query.to_string(),
+                                ..Default::default()
+                            },
+                        )
+                        .map_ok(move |response| {
+                            let lsp_symbols = response.map(|symbol_response| match symbol_response {
+                                lsp::WorkspaceSymbolResponse::Flat(flat_responses) => {
+                                    flat_responses.into_iter().map(|lsp_symbol| {
+                                        (lsp_symbol.name, lsp_symbol.kind, lsp_symbol.location)
+                                    }).collect::<Vec<_>>()
+                                }
+                                lsp::WorkspaceSymbolResponse::Nested(nested_responses) => {
+                                    nested_responses.into_iter().filter_map(|lsp_symbol| {
+                                        let location = match lsp_symbol.location {
+                                            OneOf::Left(location) => location,
+                                            OneOf::Right(_) => {
+                                                error!("Unexpected: client capabilities forbid symbol resolutions in workspace.symbol.resolveSupport");
+                                                return None
+                                            }
+                                        };
+                                        Some((lsp_symbol.name, lsp_symbol.kind, location))
+                                    }).collect::<Vec<_>>()
+                                }
+                            }).unwrap_or_default();
+
+                            (
+                                adapter,
+                                language,
+                                worktree_id,
+                                worktree_abs_path,
+                                lsp_symbols,
+                            )
+                        }),
+                );
             }
 
             cx.spawn_weak(|this, cx| async move {
                 let responses = futures::future::join_all(requests).await;
-                let this = if let Some(this) = this.upgrade(&cx) {
-                    this
-                } else {
-                    return Ok(Default::default());
+                let this = match this.upgrade(&cx) {
+                    Some(this) => this,
+                    None => return Ok(Vec::new()),
                 };
+
                 let symbols = this.read_with(&cx, |this, cx| {
                     let mut symbols = Vec::new();
-                    for (
-                        adapter,
-                        adapter_language,
-                        source_worktree_id,
-                        worktree_abs_path,
-                        lsp_symbols,
-                    ) in responses
-                    {
+                    for response in responses {
+                        let (
+                            adapter,
+                            adapter_language,
+                            source_worktree_id,
+                            worktree_abs_path,
+                            lsp_symbols,
+                        ) = match response {
+                            Ok(response) => response,
+
+                            Err(err) => {
+                                // TODO: Prompt installation validity check LSP ERROR
+                                return Vec::new();
+                            }
+                        };
+
                         symbols.extend(lsp_symbols.into_iter().filter_map(
                             |(symbol_name, symbol_kind, symbol_location)| {
                                 let abs_path = symbol_location.uri.to_file_path().ok()?;
@@ -3985,8 +4018,10 @@ impl Project {
                             },
                         ));
                     }
+
                     symbols
                 });
+
                 Ok(futures::future::join_all(symbols).await)
             })
         } else if let Some(project_id) = self.remote_id() {
@@ -4111,9 +4146,17 @@ impl Project {
             };
 
             cx.spawn(|this, mut cx| async move {
-                let resolved_completion = lang_server
+                let resolved_completion = match lang_server
                     .request::<lsp::request::ResolveCompletionItem>(completion.lsp_completion)
-                    .await?;
+                    .await
+                {
+                    Ok(resolved_completion) => resolved_completion,
+
+                    Err(err) => {
+                        // TODO: LSP ERROR
+                        return Ok(None);
+                    }
+                };
 
                 if let Some(edits) = resolved_completion.additional_text_edits {
                     let edits = this
@@ -4232,9 +4275,17 @@ impl Project {
                     .and_then(|d| d.get_mut("range"))
                 {
                     *lsp_range = serde_json::to_value(&range_to_lsp(range)).unwrap();
-                    action.lsp_action = lang_server
+                    action.lsp_action = match lang_server
                         .request::<lsp::request::CodeActionResolveRequest>(action.lsp_action)
-                        .await?;
+                        .await
+                    {
+                        Ok(lsp_action) => lsp_action,
+
+                        Err(err) => {
+                            // LSP ERROR
+                            return Err(err);
+                        }
+                    };
                 } else {
                     let actions = this
                         .update(&mut cx, |this, cx| {
@@ -4267,13 +4318,20 @@ impl Project {
                         this.last_workspace_edits_by_language_server
                             .remove(&lang_server.server_id());
                     });
-                    lang_server
+
+                    let result = lang_server
                         .request::<lsp::request::ExecuteCommand>(lsp::ExecuteCommandParams {
                             command: command.command,
                             arguments: command.arguments.unwrap_or_default(),
                             ..Default::default()
                         })
-                        .await?;
+                        .await;
+
+                    if let Err(err) = result {
+                        // TODO: LSP ERROR
+                        return Err(err);
+                    }
+
                     return Ok(this.update(&mut cx, |this, _| {
                         this.last_workspace_edits_by_language_server
                             .remove(&lang_server.server_id())
@@ -4433,7 +4491,7 @@ impl Project {
                         uri,
                         version: None,
                     },
-                    edits: edits.into_iter().map(lsp::OneOf::Left).collect(),
+                    edits: edits.into_iter().map(OneOf::Left).collect(),
                 })
             }));
         }
@@ -4503,8 +4561,8 @@ impl Project {
                     let edits = this
                         .update(cx, |this, cx| {
                             let edits = op.edits.into_iter().map(|edit| match edit {
-                                lsp::OneOf::Left(edit) => edit,
-                                lsp::OneOf::Right(edit) => edit.text_edit,
+                                OneOf::Left(edit) => edit,
+                                OneOf::Right(edit) => edit.text_edit,
                             });
                             this.edits_from_lsp(
                                 &buffer_to_edit,
@@ -4841,10 +4899,20 @@ impl Project {
                         return Ok(Default::default());
                     }
 
-                    let response = language_server
-                        .request::<R::LspRequest>(lsp_params)
-                        .await
-                        .context("lsp request failed")?;
+                    let result = language_server.request::<R::LspRequest>(lsp_params).await;
+                    let response = match result {
+                        Ok(response) => response,
+
+                        Err(err) => {
+                            log::warn!(
+                                "Generic lsp request to {} failed: {}",
+                                language_server.name(),
+                                err
+                            );
+                            return Err(err);
+                        }
+                    };
+
                     request
                         .response_from_lsp(
                             response,
@@ -7211,11 +7279,10 @@ impl Entity for Project {
             .language_servers
             .drain()
             .map(|(_, server_state)| async {
+                use LanguageServerState::*;
                 match server_state {
-                    LanguageServerState::Running { server, .. } => server.shutdown()?.await,
-                    LanguageServerState::Starting(starting_server) => {
-                        starting_server.await?.shutdown()?.await
-                    }
+                    Running { server, .. } => server.shutdown()?.await,
+                    Starting(task) | Validating(task) => task.await?.shutdown()?.await,
                 }
             })
             .collect::<Vec<_>>();

crates/util/src/util.rs 🔗

@@ -118,14 +118,15 @@ pub fn merge_non_null_json_value_into(source: serde_json::Value, target: &mut se
     }
 }
 
-pub trait ResultExt {
+pub trait ResultExt<E> {
     type Ok;
 
     fn log_err(self) -> Option<Self::Ok>;
     fn warn_on_err(self) -> Option<Self::Ok>;
+    fn inspect_error(self, func: impl FnOnce(&E)) -> Self;
 }
 
-impl<T, E> ResultExt for Result<T, E>
+impl<T, E> ResultExt<E> for Result<T, E>
 where
     E: std::fmt::Debug,
 {
@@ -152,6 +153,15 @@ where
             }
         }
     }
+
+    /// https://doc.rust-lang.org/std/result/enum.Result.html#method.inspect_err
+    fn inspect_error(self, func: impl FnOnce(&E)) -> Self {
+        if let Err(err) = &self {
+            func(err);
+        }
+
+        self
+    }
 }
 
 pub trait TryFutureExt {

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

@@ -2,6 +2,7 @@ use anyhow::{anyhow, Context, Result};
 use async_trait::async_trait;
 use futures::StreamExt;
 pub use language::*;
+use lsp::LanguageServerBinary;
 use smol::fs::{self, File};
 use std::{any::Any, path::PathBuf, sync::Arc};
 use util::fs::remove_matching;

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

@@ -2,7 +2,7 @@ use anyhow::{anyhow, Context, Result};
 use async_trait::async_trait;
 use futures::StreamExt;
 pub use language::*;
-use lsp::{CompletionItemKind, SymbolKind};
+use lsp::{CompletionItemKind, LanguageServerBinary, SymbolKind};
 use smol::fs::{self, File};
 use std::{any::Any, path::PathBuf, sync::Arc};
 use util::fs::remove_matching;

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

@@ -3,6 +3,7 @@ use async_trait::async_trait;
 use futures::StreamExt;
 pub use language::*;
 use lazy_static::lazy_static;
+use lsp::LanguageServerBinary;
 use regex::Regex;
 use smol::{fs, process};
 use std::ffi::{OsStr, OsString};

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

@@ -1,7 +1,8 @@
 use anyhow::{anyhow, Result};
 use async_trait::async_trait;
 use futures::StreamExt;
-use language::{LanguageServerBinary, LanguageServerName, LspAdapter};
+use language::{LanguageServerName, LspAdapter};
+use lsp::LanguageServerBinary;
 use node_runtime::NodeRuntime;
 use serde_json::json;
 use smol::fs;

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

@@ -3,7 +3,8 @@ use async_trait::async_trait;
 use collections::HashMap;
 use futures::{future::BoxFuture, FutureExt, StreamExt};
 use gpui::AppContext;
-use language::{LanguageRegistry, LanguageServerBinary, LanguageServerName, LspAdapter};
+use language::{LanguageRegistry, LanguageServerName, LspAdapter};
+use lsp::LanguageServerBinary;
 use node_runtime::NodeRuntime;
 use serde_json::json;
 use settings::{KeymapFile, SettingsJsonSchemaParams, SettingsStore};

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

@@ -3,7 +3,8 @@ use async_trait::async_trait;
 use collections::HashMap;
 use futures::lock::Mutex;
 use gpui::executor::Background;
-use language::{LanguageServerBinary, LanguageServerName, LspAdapter};
+use language::{LanguageServerName, LspAdapter};
+use lsp::LanguageServerBinary;
 use plugin_runtime::{Plugin, PluginBinary, PluginBuilder, WasiFn};
 use std::{any::Any, path::PathBuf, sync::Arc};
 use util::http::HttpClient;

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

@@ -3,7 +3,8 @@ use async_compression::futures::bufread::GzipDecoder;
 use async_tar::Archive;
 use async_trait::async_trait;
 use futures::{io::BufReader, StreamExt};
-use language::{LanguageServerBinary, LanguageServerName};
+use language::LanguageServerName;
+use lsp::LanguageServerBinary;
 use smol::fs;
 use std::{any::Any, env::consts, ffi::OsString, path::PathBuf, sync::Arc};
 use util::{async_iife, github::latest_github_release, http::HttpClient, ResultExt};

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

@@ -1,7 +1,8 @@
 use anyhow::{anyhow, Result};
 use async_trait::async_trait;
 use futures::StreamExt;
-use language::{LanguageServerBinary, LanguageServerName, LspAdapter};
+use language::{LanguageServerName, LspAdapter};
+use lsp::LanguageServerBinary;
 use node_runtime::NodeRuntime;
 use smol::fs;
 use std::{

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

@@ -1,6 +1,7 @@
 use anyhow::{anyhow, Result};
 use async_trait::async_trait;
-use language::{LanguageServerBinary, LanguageServerName, LspAdapter};
+use language::{LanguageServerName, LspAdapter};
+use lsp::LanguageServerBinary;
 use std::{any::Any, path::PathBuf, sync::Arc};
 use util::http::HttpClient;
 

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

@@ -4,6 +4,7 @@ use async_trait::async_trait;
 use futures::{io::BufReader, StreamExt};
 pub use language::*;
 use lazy_static::lazy_static;
+use lsp::LanguageServerBinary;
 use regex::Regex;
 use smol::fs::{self, File};
 use std::{any::Any, borrow::Cow, env::consts, path::PathBuf, str, sync::Arc};

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

@@ -4,8 +4,8 @@ use async_tar::Archive;
 use async_trait::async_trait;
 use futures::{future::BoxFuture, FutureExt};
 use gpui::AppContext;
-use language::{LanguageServerBinary, LanguageServerName, LspAdapter};
-use lsp::CodeActionKind;
+use language::{LanguageServerName, LspAdapter};
+use lsp::{CodeActionKind, LanguageServerBinary};
 use node_runtime::NodeRuntime;
 use serde_json::{json, Value};
 use smol::{fs, io::BufReader, stream::StreamExt};

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

@@ -2,9 +2,8 @@ use anyhow::{anyhow, Result};
 use async_trait::async_trait;
 use futures::{future::BoxFuture, FutureExt, StreamExt};
 use gpui::AppContext;
-use language::{
-    language_settings::all_language_settings, LanguageServerBinary, LanguageServerName, LspAdapter,
-};
+use language::{language_settings::all_language_settings, LanguageServerName, LspAdapter};
+use lsp::LanguageServerBinary;
 use node_runtime::NodeRuntime;
 use serde_json::Value;
 use smol::fs;

styles/tsconfig.json 🔗

@@ -1,4 +1,5 @@
 {
+  // "noErrorTruncation": true,
     "compilerOptions": {
         "target": "es2015",
         "module": "commonjs",