Add ability to specify binary path/args for `gopls` (#9803)

Thorsten Ball 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 `gopls`.

Example user settings for `gopls`:

```json
{
  "lsp": {
    "gopls": {
      "binary": {
        "path": "/Users/thorstenball/tmp/gopls",
        "arguments": ["-debug=0.0.0.0:8080"]
      },
    }
  }
}
```

Constraints:

* Right now this only allows ABSOLUTE paths.

Release Notes:

- Added ability to specify `gopls` binary `path` (must be absolute) and
`arguments` in user settings. Example: `{"lsp": {"gopls": {"binary":
{"path": "/my/abs/path/gopls", "arguments": ["-debug=0.0.0.0:8080"]
}}}}`

Change summary

crates/languages/src/go.rs | 47 ++++++++++++++++++++++++++++++++-------
1 file changed, 38 insertions(+), 9 deletions(-)

Detailed changes

crates/languages/src/go.rs 🔗

@@ -5,8 +5,10 @@ use gpui::{AsyncAppContext, Task};
 pub use language::*;
 use lazy_static::lazy_static;
 use lsp::LanguageServerBinary;
+use project::project_settings::{BinarySettings, ProjectSettings};
 use regex::Regex;
 use serde_json::json;
+use settings::Settings;
 use smol::{fs, process};
 use std::{
     any::Any,
@@ -28,6 +30,10 @@ fn server_binary_arguments() -> Vec<OsString> {
 #[derive(Copy, Clone)]
 pub struct GoLspAdapter;
 
+impl GoLspAdapter {
+    const SERVER_NAME: &'static str = "gopls";
+}
+
 lazy_static! {
     static ref GOPLS_VERSION_REGEX: Regex = Regex::new(r"\d+\.\d+\.\d+").unwrap();
 }
@@ -35,7 +41,7 @@ lazy_static! {
 #[async_trait(?Send)]
 impl super::LspAdapter for GoLspAdapter {
     fn name(&self) -> LanguageServerName {
-        LanguageServerName("gopls".into())
+        LanguageServerName(Self::SERVER_NAME.into())
     }
 
     async fn fetch_latest_server_version(
@@ -57,15 +63,38 @@ impl super::LspAdapter for GoLspAdapter {
     async fn check_if_user_installed(
         &self,
         delegate: &dyn LspAdapterDelegate,
-        _: &AsyncAppContext,
+        cx: &AsyncAppContext,
     ) -> Option<LanguageServerBinary> {
-        let env = delegate.shell_env().await;
-        let path = delegate.which("gopls".as_ref()).await?;
-        Some(LanguageServerBinary {
-            path,
-            arguments: server_binary_arguments(),
-            env: Some(env),
-        })
+        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("gopls".as_ref()).await?;
+            Some(LanguageServerBinary {
+                path,
+                arguments: server_binary_arguments(),
+                env: Some(env),
+            })
+        }
     }
 
     fn will_fetch_server(