Use rust-analyzer from path if possible (#12418)

Stanislav Alekseev created

Release Notes:

- Added support for looking up the `rust-analyzer` binary in `$PATH`. This allows using such tools as `asdf` and nix to configure per-folder rust installations. To enable this behavior, use the `path_lookup` key when configuring the `rust-analyzer` `binary`: `{"lsp": {"rust-analyzer": {"binary": {"path_lookup": true }}}}`.

Change summary

crates/languages/src/c.rs              | 35 ++++++++------
crates/languages/src/go.rs             | 35 ++++++++------
crates/languages/src/rust.rs           | 65 ++++++++++++++++++---------
crates/project/src/project_settings.rs |  1 
4 files changed, 84 insertions(+), 52 deletions(-)

Detailed changes

crates/languages/src/c.rs 🔗

@@ -35,12 +35,12 @@ impl super::LspAdapter for CLspAdapter {
                 .and_then(|s| s.binary.clone())
         });
 
-        if let Ok(Some(BinarySettings {
-            path: Some(path),
-            arguments,
-        })) = configured_binary
-        {
-            Some(LanguageServerBinary {
+        match configured_binary {
+            Ok(Some(BinarySettings {
+                path: Some(path),
+                arguments,
+                ..
+            })) => Some(LanguageServerBinary {
                 path: path.into(),
                 arguments: arguments
                     .unwrap_or_default()
@@ -48,15 +48,20 @@ impl super::LspAdapter for CLspAdapter {
                     .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),
-            })
+            }),
+            Ok(Some(BinarySettings {
+                path_lookup: Some(false),
+                ..
+            })) => None,
+            _ => {
+                let env = delegate.shell_env().await;
+                let path = delegate.which(Self::SERVER_NAME.as_ref()).await?;
+                Some(LanguageServerBinary {
+                    path,
+                    arguments: vec![],
+                    env: Some(env),
+                })
+            }
         }
     }
 

crates/languages/src/go.rs 🔗

@@ -78,12 +78,12 @@ impl super::LspAdapter for GoLspAdapter {
                 .and_then(|s| s.binary.clone())
         });
 
-        if let Ok(Some(BinarySettings {
-            path: Some(path),
-            arguments,
-        })) = configured_binary
-        {
-            Some(LanguageServerBinary {
+        match configured_binary {
+            Ok(Some(BinarySettings {
+                path: Some(path),
+                arguments,
+                ..
+            })) => Some(LanguageServerBinary {
                 path: path.into(),
                 arguments: arguments
                     .unwrap_or_default()
@@ -91,15 +91,20 @@ impl super::LspAdapter for GoLspAdapter {
                     .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: server_binary_arguments(),
-                env: Some(env),
-            })
+            }),
+            Ok(Some(BinarySettings {
+                path_lookup: Some(false),
+                ..
+            })) => None,
+            _ => {
+                let env = delegate.shell_env().await;
+                let path = delegate.which(Self::SERVER_NAME.as_ref()).await?;
+                Some(LanguageServerBinary {
+                    path,
+                    arguments: server_binary_arguments(),
+                    env: Some(env),
+                })
+            }
         }
     }
 

crates/languages/src/rust.rs 🔗

@@ -7,7 +7,7 @@ use http::github::{latest_github_release, GitHubLspBinaryVersion};
 pub use language::*;
 use lazy_static::lazy_static;
 use lsp::LanguageServerBinary;
-use project::project_settings::ProjectSettings;
+use project::project_settings::{BinarySettings, ProjectSettings};
 use regex::Regex;
 use settings::Settings;
 use smol::fs::{self, File};
@@ -35,29 +35,50 @@ impl LspAdapter for RustLspAdapter {
 
     async fn check_if_user_installed(
         &self,
-        _delegate: &dyn LspAdapterDelegate,
+        delegate: &dyn LspAdapterDelegate,
         cx: &AsyncAppContext,
     ) -> Option<LanguageServerBinary> {
-        let binary = cx
-            .update(|cx| {
-                ProjectSettings::get_global(cx)
-                    .lsp
-                    .get(Self::SERVER_NAME)
-                    .and_then(|s| s.binary.clone())
-            })
-            .ok()??;
-
-        let path = binary.path?;
-        Some(LanguageServerBinary {
-            path: path.into(),
-            arguments: binary
-                .arguments
-                .unwrap_or_default()
-                .iter()
-                .map(|arg| arg.into())
-                .collect(),
-            env: None,
-        })
+        let configured_binary = cx.update(|cx| {
+            ProjectSettings::get_global(cx)
+                .lsp
+                .get(Self::SERVER_NAME)
+                .and_then(|s| s.binary.clone())
+        });
+
+        match configured_binary {
+            Ok(Some(BinarySettings {
+                path,
+                arguments,
+                path_lookup,
+            })) => {
+                let (path, env) = match (path, path_lookup) {
+                    (Some(path), lookup) => {
+                        if lookup.is_some() {
+                            log::warn!(
+                                "Both `path` and `path_lookup` are set, ignoring `path_lookup`"
+                            );
+                        }
+                        (Some(path.into()), None)
+                    }
+                    (None, Some(true)) => {
+                        let path = delegate.which(Self::SERVER_NAME.as_ref()).await?;
+                        let env = delegate.shell_env().await;
+                        (Some(path), Some(env))
+                    }
+                    (None, Some(false)) | (None, None) => (None, None),
+                };
+                path.map(|path| LanguageServerBinary {
+                    path,
+                    arguments: arguments
+                        .unwrap_or_default()
+                        .iter()
+                        .map(|arg| arg.into())
+                        .collect(),
+                    env,
+                })
+            }
+            _ => None,
+        }
     }
 
     async fn fetch_latest_server_version(

crates/project/src/project_settings.rs 🔗

@@ -94,6 +94,7 @@ const fn true_value() -> bool {
 pub struct BinarySettings {
     pub path: Option<String>,
     pub arguments: Option<Vec<String>>,
+    pub path_lookup: Option<bool>,
 }
 
 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]