Add ability to specify binary path/args for clangd (#10608)

Henrique Ferreiro created

This uses the language server settings added in #9293 to allow users to
specify the binary path and arguments with which to start up `clangd`.

Example user settings for `clangd`:

```json
{
  "lsp": {
    "clangd": {
      "binary": {
        "path": "/usr/bin/clangd",
        "arguments": ["--log=verbose"]
      },
    }
  }
}
```

Constraints:

* Right now this only allows ABSOLUTE paths.

Release Notes:

- Added ability to specify `clangd` binary `path` (must be absolute) and
`arguments` in user settings. Example: `{"lsp": {"clangd": {"binary":
{"path": "/usr/bin/clangd", "arguments": ["--log=verbose"] }}}}`

Change summary

crates/languages/src/c.rs  | 59 +++++++++++++++++++++++++++++----------
crates/languages/src/go.rs |  2 
2 files changed, 45 insertions(+), 16 deletions(-)

Detailed changes

crates/languages/src/c.rs 🔗

@@ -4,6 +4,8 @@ use futures::StreamExt;
 use gpui::AsyncAppContext;
 pub use language::*;
 use lsp::LanguageServerBinary;
+use project::project_settings::{BinarySettings, ProjectSettings};
+use settings::Settings;
 use smol::fs::{self, File};
 use std::{any::Any, env::consts, path::PathBuf, sync::Arc};
 use util::{
@@ -14,10 +16,51 @@ use util::{
 
 pub struct CLspAdapter;
 
+impl CLspAdapter {
+    const SERVER_NAME: &'static str = "clangd";
+}
+
 #[async_trait(?Send)]
 impl super::LspAdapter for CLspAdapter {
     fn name(&self) -> LanguageServerName {
-        LanguageServerName("clangd".into())
+        LanguageServerName(Self::SERVER_NAME.into())
+    }
+
+    async fn check_if_user_installed(
+        &self,
+        delegate: &dyn LspAdapterDelegate,
+        cx: &AsyncAppContext,
+    ) -> Option<LanguageServerBinary> {
+        let configured_binary = cx.update(|cx| {
+            ProjectSettings::get_global(cx)
+                .lsp
+                .get(Self::SERVER_NAME)
+                .and_then(|s| s.binary.clone())
+        });
+
+        if let Ok(Some(BinarySettings {
+            path: Some(path),
+            arguments,
+        })) = configured_binary
+        {
+            Some(LanguageServerBinary {
+                path: path.into(),
+                arguments: arguments
+                    .unwrap_or_default()
+                    .iter()
+                    .map(|arg| arg.into())
+                    .collect(),
+                env: None,
+            })
+        } else {
+            let env = delegate.shell_env().await;
+            let path = delegate.which(Self::SERVER_NAME.as_ref()).await?;
+            Some(LanguageServerBinary {
+                path,
+                arguments: vec![],
+                env: Some(env),
+            })
+        }
     }
 
     async fn fetch_latest_server_version(
@@ -45,20 +88,6 @@ impl super::LspAdapter for CLspAdapter {
         Ok(Box::new(version) as Box<_>)
     }
 
-    async fn check_if_user_installed(
-        &self,
-        delegate: &dyn LspAdapterDelegate,
-        _: &AsyncAppContext,
-    ) -> Option<LanguageServerBinary> {
-        let env = delegate.shell_env().await;
-        let path = delegate.which("clangd".as_ref()).await?;
-        Some(LanguageServerBinary {
-            path,
-            arguments: vec![],
-            env: Some(env),
-        })
-    }
-
     async fn fetch_server_binary(
         &self,
         version: Box<dyn 'static + Send + Any>,

crates/languages/src/go.rs 🔗

@@ -88,7 +88,7 @@ impl super::LspAdapter for GoLspAdapter {
             })
         } else {
             let env = delegate.shell_env().await;
-            let path = delegate.which("gopls".as_ref()).await?;
+            let path = delegate.which(Self::SERVER_NAME.as_ref()).await?;
             Some(LanguageServerBinary {
                 path,
                 arguments: server_binary_arguments(),