From 42ac9880c6f1d854d2dfe8b668c2ffd5dd317537 Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Fri, 23 Feb 2024 13:39:14 +0100 Subject: [PATCH] Detect and possibly use user-installed `gopls` / `zls` language servers (#8188) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After a lot of back-and-forth, this is a small attempt to implement solutions (1) and (3) in https://github.com/zed-industries/zed/issues/7902. The goal is to have a minimal change that helps users get started with Zed, until we have extensions ready. Release Notes: - Added detection of user-installed `gopls` to Go language server adapter. If a user has `gopls` in `$PATH` when opening a worktree, it will be used. - Added detection of user-installed `zls` to Zig language server adapter. If a user has `zls` in `$PATH` when opening a worktree, it will be used. Example: I don't have `go` installed globally, but I do have `gopls`: ``` ~ $ which go go not found ~ $ which gopls /Users/thorstenball/code/go/bin/gopls ``` But I do have `go` in a project's directory: ``` ~/tmp/go-testing φ which go /Users/thorstenball/.local/share/mise/installs/go/1.21.5/go/bin/go ~/tmp/go-testing φ which gopls /Users/thorstenball/code/go/bin/gopls ``` With current Zed when I run `zed ~/tmp/go-testing`, I'd get the dreaded error: ![screenshot-2024-02-23-11 14 08@2x](https://github.com/zed-industries/zed/assets/1185253/822ea59b-c63e-4102-a50e-75501cc4e0e3) But with the changes in this PR, it works: ``` [2024-02-23T11:14:42+01:00 INFO language::language_registry] starting language server "gopls", path: "/Users/thorstenball/tmp/go-testing", id: 1 [2024-02-23T11:14:42+01:00 INFO language::language_registry] found user-installed language server for Go. path: "/Users/thorstenball/code/go/bin/gopls", arguments: ["-mode=stdio"] [2024-02-23T11:14:42+01:00 INFO lsp] starting language server. binary path: "/Users/thorstenball/code/go/bin/gopls", working directory: "/Users/thorstenball/tmp/go-testing", args: ["-mode=stdio"] ``` --------- Co-authored-by: Antonio --- Cargo.lock | 24 +++- Cargo.toml | 1 + crates/copilot/src/copilot.rs | 2 + crates/language/src/language.rs | 22 ++++ crates/language/src/language_registry.rs | 143 +++++++++++++++++------ crates/lsp/src/lsp.rs | 2 + crates/prettier/src/prettier.rs | 1 + crates/project/Cargo.toml | 1 + crates/project/src/project.rs | 109 +++++++++++++++-- crates/zed/src/languages/astro.rs | 2 + crates/zed/src/languages/c.rs | 2 + crates/zed/src/languages/clojure.rs | 3 + crates/zed/src/languages/csharp.rs | 2 + crates/zed/src/languages/css.rs | 2 + crates/zed/src/languages/dart.rs | 1 + crates/zed/src/languages/deno.rs | 2 + crates/zed/src/languages/dockerfile.rs | 2 + crates/zed/src/languages/elixir.rs | 7 ++ crates/zed/src/languages/elm.rs | 2 + crates/zed/src/languages/erlang.rs | 2 + crates/zed/src/languages/gleam.rs | 2 + crates/zed/src/languages/go.rs | 22 ++++ crates/zed/src/languages/haskell.rs | 1 + crates/zed/src/languages/html.rs | 2 + crates/zed/src/languages/json.rs | 2 + crates/zed/src/languages/lua.rs | 2 + crates/zed/src/languages/nu.rs | 1 + crates/zed/src/languages/ocaml.rs | 1 + crates/zed/src/languages/php.rs | 2 + crates/zed/src/languages/prisma.rs | 2 + crates/zed/src/languages/purescript.rs | 2 + crates/zed/src/languages/python.rs | 2 + crates/zed/src/languages/ruby.rs | 1 + crates/zed/src/languages/rust.rs | 2 + crates/zed/src/languages/svelte.rs | 2 + crates/zed/src/languages/tailwind.rs | 2 + crates/zed/src/languages/toml.rs | 2 + crates/zed/src/languages/typescript.rs | 5 + crates/zed/src/languages/uiua.rs | 1 + crates/zed/src/languages/vue.rs | 2 + crates/zed/src/languages/yaml.rs | 2 + crates/zed/src/languages/zig.rs | 24 ++++ 42 files changed, 369 insertions(+), 47 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 89ef40db21..4fdd5d6bec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1351,7 +1351,7 @@ dependencies = [ "rustc-hash", "shlex", "syn 2.0.48", - "which", + "which 4.4.2", ] [[package]] @@ -4348,11 +4348,11 @@ dependencies = [ [[package]] name = "home" -version = "0.5.5" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -6942,6 +6942,7 @@ dependencies = [ "toml 0.8.10", "unindent", "util", + "which 6.0.0", ] [[package]] @@ -7051,7 +7052,7 @@ dependencies = [ "prost-types 0.9.0", "regex", "tempfile", - "which", + "which 4.4.2", ] [[package]] @@ -11425,6 +11426,19 @@ dependencies = [ "rustix 0.38.30", ] +[[package]] +name = "which" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fa5e0c10bf77f44aac573e498d1a82d5fbd5e91f6fc0a99e7be4b38e85e101c" +dependencies = [ + "either", + "home", + "once_cell", + "rustix 0.38.30", + "windows-sys 0.52.0", +] + [[package]] name = "whoami" version = "1.4.1" diff --git a/Cargo.toml b/Cargo.toml index e15d0b0874..daed781727 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -279,6 +279,7 @@ unindent = "0.1.7" url = "2.2" uuid = { version = "1.1.2", features = ["v4"] } wasmtime = "16" +which = "6.0.0" sys-locale = "0.3.1" [patch.crates-io] diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index 7d2e626476..39e7465b26 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -428,6 +428,8 @@ impl Copilot { let binary = LanguageServerBinary { path: node_path, arguments, + // TODO: We could set HTTP_PROXY etc here and fix the copilot issue. + env: None, }; let server = LanguageServer::new( diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index e9c1a83bed..668f038fb8 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -38,6 +38,7 @@ use serde_json::Value; use std::{ any::Any, cell::RefCell, + ffi::OsString, fmt::Debug, hash::Hash, mem, @@ -140,6 +141,14 @@ impl CachedLspAdapter { }) } + pub fn check_if_user_installed( + &self, + delegate: &Arc, + cx: &mut AsyncAppContext, + ) -> Option>> { + self.adapter.check_if_user_installed(delegate, cx) + } + pub async fn fetch_latest_server_version( &self, delegate: &dyn LspAdapterDelegate, @@ -240,6 +249,11 @@ impl CachedLspAdapter { pub trait LspAdapterDelegate: Send + Sync { fn show_notification(&self, message: &str, cx: &mut AppContext); fn http_client(&self) -> Arc; + fn which_command( + &self, + command: OsString, + cx: &AppContext, + ) -> Task)>>; } #[async_trait] @@ -248,6 +262,14 @@ pub trait LspAdapter: 'static + Send + Sync { fn short_name(&self) -> &'static str; + fn check_if_user_installed( + &self, + _: &Arc, + _: &mut AsyncAppContext, + ) -> Option>> { + None + } + async fn fetch_latest_server_version( &self, delegate: &dyn LspAdapterDelegate, diff --git a/crates/language/src/language_registry.rs b/crates/language/src/language_registry.rs index fbb72a21fb..0f968c050f 100644 --- a/crates/language/src/language_registry.rs +++ b/crates/language/src/language_registry.rs @@ -558,34 +558,41 @@ impl LanguageRegistry { let task = { let container_dir = container_dir.clone(); cx.spawn(move |mut cx| async move { - login_shell_env_loaded.await; + // First we check whether the adapter can give us a user-installed binary. + // If so, we do *not* want to cache that, because each worktree might give us a different + // binary: + // + // worktree 1: user-installed at `.bin/gopls` + // worktree 2: user-installed at `~/bin/gopls` + // worktree 3: no gopls found in PATH -> fallback to Zed installation + // + // We only want to cache when we fall back to the global one, + // because we don't want to download and overwrite our global one + // for each worktree we might have open. - let entry = this - .lsp_binary_paths - .lock() - .entry(adapter.name.clone()) - .or_insert_with(|| { - let adapter = adapter.clone(); - let language = language.clone(); - let delegate = delegate.clone(); - cx.spawn(|cx| { - get_binary( - adapter, - language, - delegate, - container_dir, - lsp_binary_statuses, - cx, - ) - .map_err(Arc::new) - }) - .shared() - }) - .clone(); + let user_binary_task = check_user_installed_binary( + adapter.clone(), + language.clone(), + delegate.clone(), + &mut cx, + ); + let binary = if let Some(user_binary) = user_binary_task.await { + user_binary + } else { + // If we want to install a binary globally, we need to wait for + // the login shell to be set on our process. + login_shell_env_loaded.await; - let binary = match entry.await { - Ok(binary) => binary, - Err(err) => anyhow::bail!("{err}"), + get_or_install_binary( + this, + &adapter, + language, + &delegate, + &cx, + container_dir, + lsp_binary_statuses, + ) + .await? }; if let Some(task) = adapter.will_start_server(&delegate, &mut cx) { @@ -724,6 +731,62 @@ impl LspBinaryStatusSender { } } +async fn check_user_installed_binary( + adapter: Arc, + language: Arc, + delegate: Arc, + cx: &mut AsyncAppContext, +) -> Option { + let Some(task) = adapter.check_if_user_installed(&delegate, cx) else { + return None; + }; + + task.await.and_then(|binary| { + log::info!( + "found user-installed language server for {}. path: {:?}, arguments: {:?}", + language.name(), + binary.path, + binary.arguments + ); + Some(binary) + }) +} + +async fn get_or_install_binary( + registry: Arc, + adapter: &Arc, + language: Arc, + delegate: &Arc, + cx: &AsyncAppContext, + container_dir: Arc, + lsp_binary_statuses: LspBinaryStatusSender, +) -> Result { + let entry = registry + .lsp_binary_paths + .lock() + .entry(adapter.name.clone()) + .or_insert_with(|| { + let adapter = adapter.clone(); + let language = language.clone(); + let delegate = delegate.clone(); + cx.spawn(|cx| { + get_binary( + adapter, + language, + delegate, + container_dir, + lsp_binary_statuses, + cx, + ) + .map_err(Arc::new) + }) + .shared() + }) + .clone(); + + entry.await.map_err(|err| anyhow!("{:?}", err)) +} + async fn get_binary( adapter: Arc, language: Arc, @@ -757,15 +820,20 @@ async fn get_binary( .await { statuses.send(language.clone(), LanguageServerBinaryStatus::Cached); - return Ok(binary); - } else { - statuses.send( - language.clone(), - LanguageServerBinaryStatus::Failed { - error: format!("{:?}", error), - }, + log::info!( + "failed to fetch newest version of language server {:?}. falling back to using {:?}", + adapter.name, + binary.path.display() ); + return Ok(binary); } + + statuses.send( + language.clone(), + LanguageServerBinaryStatus::Failed { + error: format!("{:?}", error), + }, + ); } binary @@ -779,14 +847,23 @@ async fn fetch_latest_binary( lsp_binary_statuses_tx: LspBinaryStatusSender, ) -> Result { let container_dir: Arc = container_dir.into(); + lsp_binary_statuses_tx.send( language.clone(), LanguageServerBinaryStatus::CheckingForUpdate, ); + log::info!( + "querying GitHub for latest version of language server {:?}", + adapter.name.0 + ); let version_info = adapter.fetch_latest_server_version(delegate).await?; lsp_binary_statuses_tx.send(language.clone(), LanguageServerBinaryStatus::Downloading); + log::info!( + "checking if Zed already installed or fetching version for language server {:?}", + adapter.name.0 + ); let binary = adapter .fetch_server_binary(version_info, container_dir.to_path_buf(), delegate) .await?; diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index b0379049a3..caefa57c86 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -55,6 +55,7 @@ pub enum IoKind { pub struct LanguageServerBinary { pub path: PathBuf, pub arguments: Vec, + pub env: Option>, } /// A running language server process. @@ -189,6 +190,7 @@ impl LanguageServer { let mut server = process::Command::new(&binary.path) .current_dir(working_dir) .args(binary.arguments) + .envs(binary.env.unwrap_or_default()) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 119901cf07..5aa78eef32 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -192,6 +192,7 @@ impl Prettier { LanguageServerBinary { path: node_path, arguments: vec![prettier_server.into(), prettier_dir.as_path().into()], + env: None, }, Path::new("/"), None, diff --git a/crates/project/Cargo.toml b/crates/project/Cargo.toml index c395bc92f0..e83a4ed658 100644 --- a/crates/project/Cargo.toml +++ b/crates/project/Cargo.toml @@ -65,6 +65,7 @@ text.workspace = true thiserror.workspace = true toml.workspace = true util.workspace = true +which.workspace = true [dev-dependencies] client = { workspace = true, features = ["test-support"] } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 0c82a40c16..5b11c7622d 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -71,6 +71,8 @@ use smol::lock::Semaphore; use std::{ cmp::{self, Ordering}, convert::TryInto, + env, + ffi::OsString, hash::Hash, mem, num::NonZeroU32, @@ -504,11 +506,6 @@ pub enum FormatTrigger { Manual, } -struct ProjectLspAdapterDelegate { - project: Model, - http_client: Arc, -} - // Currently, formatting operations are represented differently depending on // whether they come from a language server or an external command. enum FormatOperation { @@ -2803,7 +2800,7 @@ impl Project { fn start_language_server( &mut self, - worktree: &Model, + worktree_handle: &Model, adapter: Arc, language: Arc, cx: &mut ModelContext, @@ -2812,7 +2809,7 @@ impl Project { return; } - let worktree = worktree.read(cx); + let worktree = worktree_handle.read(cx); let worktree_id = worktree.id(); let worktree_path = worktree.abs_path(); let key = (worktree_id, adapter.name.clone()); @@ -2826,7 +2823,7 @@ impl Project { language.clone(), adapter.clone(), Arc::clone(&worktree_path), - ProjectLspAdapterDelegate::new(self, cx), + ProjectLspAdapterDelegate::new(self, worktree_handle, cx), cx, ) { Some(pending_server) => pending_server, @@ -9298,10 +9295,17 @@ impl> From<(WorktreeId, P)> for ProjectPath { } } +struct ProjectLspAdapterDelegate { + project: Model, + worktree: Model, + http_client: Arc, +} + impl ProjectLspAdapterDelegate { - fn new(project: &Project, cx: &ModelContext) -> Arc { + fn new(project: &Project, worktree: &Model, cx: &ModelContext) -> Arc { Arc::new(Self { project: cx.handle(), + worktree: worktree.clone(), http_client: project.client.http_client(), }) } @@ -9316,6 +9320,41 @@ impl LspAdapterDelegate for ProjectLspAdapterDelegate { fn http_client(&self) -> Arc { self.http_client.clone() } + + fn which_command( + &self, + command: OsString, + cx: &AppContext, + ) -> Task)>> { + let worktree_abs_path = self.worktree.read(cx).abs_path(); + let command = command.to_owned(); + + cx.background_executor().spawn(async move { + let shell_env = load_shell_environment(&worktree_abs_path) + .await + .with_context(|| { + format!( + "failed to determine load login shell environment in {worktree_abs_path:?}" + ) + }) + .log_err(); + + if let Some(shell_env) = shell_env { + let shell_path = shell_env.get("PATH"); + match which::which_in(&command, shell_path, &worktree_abs_path) { + Ok(command_path) => Some((command_path, shell_env)), + Err(error) => { + log::warn!( + "failed to determine path for command {:?} in env {shell_env:?}: {error}", command.to_string_lossy() + ); + None + } + } + } else { + None + } + }) + } } fn serialize_symbol(symbol: &Symbol) -> proto::Symbol { @@ -9423,3 +9462,55 @@ fn include_text(server: &lsp::LanguageServer) -> bool { }) .unwrap_or(false) } + +async fn load_shell_environment(dir: &Path) -> Result> { + let marker = "ZED_SHELL_START"; + let shell = env::var("SHELL").context( + "SHELL environment variable is not assigned so we can't source login environment variables", + )?; + let output = smol::process::Command::new(&shell) + .args([ + "-i", + "-c", + // What we're doing here is to spawn a shell and then `cd` into + // the project directory to get the env in there as if the user + // `cd`'d into it. We do that because tools like direnv, asdf, ... + // hook into `cd` and only set up the env after that. + // + // The `exit 0` is the result of hours of debugging, trying to find out + // why running this command here, without `exit 0`, would mess + // up signal process for our process so that `ctrl-c` doesn't work + // anymore. + // We still don't know why `$SHELL -l -i -c '/usr/bin/env -0'` would + // do that, but it does, and `exit 0` helps. + &format!("cd {dir:?}; echo {marker}; /usr/bin/env -0; exit 0;"), + ]) + .output() + .await + .context("failed to spawn login shell to source login environment variables")?; + + anyhow::ensure!( + output.status.success(), + "login shell exited with error {:?}", + output.status + ); + + let stdout = String::from_utf8_lossy(&output.stdout); + let env_output_start = stdout.find(marker).ok_or_else(|| { + anyhow!( + "failed to parse output of `env` command in login shell: {}", + stdout + ) + })?; + + let mut parsed_env = HashMap::default(); + let env_output = &stdout[env_output_start + marker.len()..]; + for line in env_output.split_terminator('\0') { + if let Some(separator_index) = line.find('=') { + let key = line[..separator_index].to_string(); + let value = line[separator_index + 1..].to_string(); + parsed_env.insert(key, value); + } + } + Ok(parsed_env) +} diff --git a/crates/zed/src/languages/astro.rs b/crates/zed/src/languages/astro.rs index 88fd30950b..3ce39b8fc3 100644 --- a/crates/zed/src/languages/astro.rs +++ b/crates/zed/src/languages/astro.rs @@ -71,6 +71,7 @@ impl LspAdapter for AstroLspAdapter { Ok(LanguageServerBinary { path: self.node.binary_path().await?, + env: None, arguments: server_binary_arguments(&server_path), }) } @@ -122,6 +123,7 @@ async fn get_cached_server_binary( if server_path.exists() { Ok(LanguageServerBinary { path: node.binary_path().await?, + env: None, arguments: server_binary_arguments(&server_path), }) } else { diff --git a/crates/zed/src/languages/c.rs b/crates/zed/src/languages/c.rs index 974a95766b..60d0b9b63b 100644 --- a/crates/zed/src/languages/c.rs +++ b/crates/zed/src/languages/c.rs @@ -84,6 +84,7 @@ impl super::LspAdapter for CLspAdapter { Ok(LanguageServerBinary { path: binary_path, + env: None, arguments: vec![], }) } @@ -260,6 +261,7 @@ async fn get_cached_server_binary(container_dir: PathBuf) -> Option Option Option { Some(LanguageServerBinary { path: "dart".into(), + env: None, arguments: vec!["language-server".into(), "--protocol=lsp".into()], }) } diff --git a/crates/zed/src/languages/deno.rs b/crates/zed/src/languages/deno.rs index 3e7b8754e1..95493b2912 100644 --- a/crates/zed/src/languages/deno.rs +++ b/crates/zed/src/languages/deno.rs @@ -134,6 +134,7 @@ impl LspAdapter for DenoLspAdapter { Ok(LanguageServerBinary { path: binary_path, + env: None, arguments: deno_server_binary_arguments(), }) } @@ -220,6 +221,7 @@ async fn get_cached_server_binary(container_dir: PathBuf) -> Option Option Option { Some(LanguageServerBinary { path: "erlang_ls".into(), + env: None, arguments: vec![], }) } @@ -52,6 +53,7 @@ impl LspAdapter for ErlangLspAdapter { async fn installation_test_binary(&self, _: PathBuf) -> Option { Some(LanguageServerBinary { path: "erlang_ls".into(), + env: None, arguments: vec!["--version".into()], }) } diff --git a/crates/zed/src/languages/gleam.rs b/crates/zed/src/languages/gleam.rs index 2ff959655c..6efb3cd38a 100644 --- a/crates/zed/src/languages/gleam.rs +++ b/crates/zed/src/languages/gleam.rs @@ -81,6 +81,7 @@ impl LspAdapter for GleamLspAdapter { Ok(LanguageServerBinary { path: binary_path, + env: None, arguments: server_binary_arguments(), }) } @@ -116,6 +117,7 @@ async fn get_cached_server_binary(container_dir: PathBuf) -> Option) } + fn check_if_user_installed( + &self, + delegate: &Arc, + cx: &mut AsyncAppContext, + ) -> Option>> { + let delegate = delegate.clone(); + + Some(cx.spawn(|cx| async move { + match cx.update(|cx| delegate.which_command(OsString::from("gopls"), cx)) { + Ok(task) => task.await.map(|(path, env)| LanguageServerBinary { + path, + arguments: server_binary_arguments(), + env: Some(env), + }), + Err(_) => None, + } + })) + } + fn will_fetch_server( &self, delegate: &Arc, @@ -107,6 +126,7 @@ impl super::LspAdapter for GoLspAdapter { return Ok(LanguageServerBinary { path: binary_path.to_path_buf(), arguments: server_binary_arguments(), + env: None, }); } } @@ -154,6 +174,7 @@ impl super::LspAdapter for GoLspAdapter { Ok(LanguageServerBinary { path: binary_path.to_path_buf(), arguments: server_binary_arguments(), + env: None, }) } @@ -372,6 +393,7 @@ async fn get_cached_server_binary(container_dir: PathBuf) -> Option Option { Some(LanguageServerBinary { path: "haskell-language-server-wrapper".into(), + env: None, arguments: vec!["lsp".into()], }) } diff --git a/crates/zed/src/languages/html.rs b/crates/zed/src/languages/html.rs index 02c6fabaad..0177d0956e 100644 --- a/crates/zed/src/languages/html.rs +++ b/crates/zed/src/languages/html.rs @@ -72,6 +72,7 @@ impl LspAdapter for HtmlLspAdapter { Ok(LanguageServerBinary { path: self.node.binary_path().await?, + env: None, arguments: server_binary_arguments(&server_path), }) } @@ -116,6 +117,7 @@ async fn get_cached_server_binary( if server_path.exists() { Ok(LanguageServerBinary { path: node.binary_path().await?, + env: None, arguments: server_binary_arguments(&server_path), }) } else { diff --git a/crates/zed/src/languages/json.rs b/crates/zed/src/languages/json.rs index a24bb4fd9e..2d2c4d1e2a 100644 --- a/crates/zed/src/languages/json.rs +++ b/crates/zed/src/languages/json.rs @@ -122,6 +122,7 @@ impl LspAdapter for JsonLspAdapter { Ok(LanguageServerBinary { path: self.node.binary_path().await?, + env: None, arguments: server_binary_arguments(&server_path), }) } @@ -177,6 +178,7 @@ async fn get_cached_server_binary( if server_path.exists() { Ok(LanguageServerBinary { path: node.binary_path().await?, + env: None, arguments: server_binary_arguments(&server_path), }) } else { diff --git a/crates/zed/src/languages/lua.rs b/crates/zed/src/languages/lua.rs index 95d73f2566..eeb9b7639d 100644 --- a/crates/zed/src/languages/lua.rs +++ b/crates/zed/src/languages/lua.rs @@ -94,6 +94,7 @@ impl super::LspAdapter for LuaLspAdapter { } Ok(LanguageServerBinary { path: binary_path, + env: None, arguments: Vec::new(), }) } @@ -138,6 +139,7 @@ async fn get_cached_server_binary(container_dir: PathBuf) -> Option Option { Some(LanguageServerBinary { path: "nu".into(), + env: None, arguments: vec!["--lsp".into()], }) } diff --git a/crates/zed/src/languages/ocaml.rs b/crates/zed/src/languages/ocaml.rs index 9878b89e33..3a292884c0 100644 --- a/crates/zed/src/languages/ocaml.rs +++ b/crates/zed/src/languages/ocaml.rs @@ -47,6 +47,7 @@ impl LspAdapter for OCamlLspAdapter { ) -> Option { Some(LanguageServerBinary { path: "ocamllsp".into(), + env: None, arguments: vec![], }) } diff --git a/crates/zed/src/languages/php.rs b/crates/zed/src/languages/php.rs index d952e4a2fb..a41cd13760 100644 --- a/crates/zed/src/languages/php.rs +++ b/crates/zed/src/languages/php.rs @@ -69,6 +69,7 @@ impl LspAdapter for IntelephenseLspAdapter { } Ok(LanguageServerBinary { path: self.node.binary_path().await?, + env: None, arguments: intelephense_server_binary_arguments(&server_path), }) } @@ -126,6 +127,7 @@ async fn get_cached_server_binary( if server_path.exists() { Ok(LanguageServerBinary { path: node.binary_path().await?, + env: None, arguments: intelephense_server_binary_arguments(&server_path), }) } else { diff --git a/crates/zed/src/languages/prisma.rs b/crates/zed/src/languages/prisma.rs index e84cf8d3e9..d07355cb16 100644 --- a/crates/zed/src/languages/prisma.rs +++ b/crates/zed/src/languages/prisma.rs @@ -70,6 +70,7 @@ impl LspAdapter for PrismaLspAdapter { Ok(LanguageServerBinary { path: self.node.binary_path().await?, + env: None, arguments: server_binary_arguments(&server_path), }) } @@ -112,6 +113,7 @@ async fn get_cached_server_binary( if server_path.exists() { Ok(LanguageServerBinary { path: node.binary_path().await?, + env: None, arguments: server_binary_arguments(&server_path), }) } else { diff --git a/crates/zed/src/languages/purescript.rs b/crates/zed/src/languages/purescript.rs index 150f154633..0aea798041 100644 --- a/crates/zed/src/languages/purescript.rs +++ b/crates/zed/src/languages/purescript.rs @@ -74,6 +74,7 @@ impl LspAdapter for PurescriptLspAdapter { Ok(LanguageServerBinary { path: self.node.binary_path().await?, + env: None, arguments: server_binary_arguments(&server_path), }) } @@ -127,6 +128,7 @@ async fn get_cached_server_binary( if server_path.exists() { Ok(LanguageServerBinary { path: node.binary_path().await?, + env: None, arguments: server_binary_arguments(&server_path), }) } else { diff --git a/crates/zed/src/languages/python.rs b/crates/zed/src/languages/python.rs index 216957df66..688bddfeec 100644 --- a/crates/zed/src/languages/python.rs +++ b/crates/zed/src/languages/python.rs @@ -62,6 +62,7 @@ impl LspAdapter for PythonLspAdapter { Ok(LanguageServerBinary { path: self.node.binary_path().await?, + env: None, arguments: server_binary_arguments(&server_path), }) } @@ -167,6 +168,7 @@ async fn get_cached_server_binary( if server_path.exists() { Some(LanguageServerBinary { path: node.binary_path().await.log_err()?, + env: None, arguments: server_binary_arguments(&server_path), }) } else { diff --git a/crates/zed/src/languages/ruby.rs b/crates/zed/src/languages/ruby.rs index 69294a5470..068a6e97d0 100644 --- a/crates/zed/src/languages/ruby.rs +++ b/crates/zed/src/languages/ruby.rs @@ -39,6 +39,7 @@ impl LspAdapter for RubyLanguageServer { ) -> Option { Some(LanguageServerBinary { path: "solargraph".into(), + env: None, arguments: vec!["stdio".into()], }) } diff --git a/crates/zed/src/languages/rust.rs b/crates/zed/src/languages/rust.rs index b5a1bdbde0..0c73ff9881 100644 --- a/crates/zed/src/languages/rust.rs +++ b/crates/zed/src/languages/rust.rs @@ -89,6 +89,7 @@ impl LspAdapter for RustLspAdapter { Ok(LanguageServerBinary { path: destination_path, + env: None, arguments: Default::default(), }) } @@ -296,6 +297,7 @@ async fn get_cached_server_binary(container_dir: PathBuf) -> Option Option Option { Some(LanguageServerBinary { path: "uiua".into(), + env: None, arguments: vec!["lsp".into()], }) } diff --git a/crates/zed/src/languages/vue.rs b/crates/zed/src/languages/vue.rs index fa86d68eaa..2fc9a91907 100644 --- a/crates/zed/src/languages/vue.rs +++ b/crates/zed/src/languages/vue.rs @@ -118,6 +118,7 @@ impl super::LspAdapter for VueLspAdapter { *self.typescript_install_path.lock() = Some(ts_path); Ok(LanguageServerBinary { path: self.node.binary_path().await?, + env: None, arguments: vue_server_binary_arguments(&server_path), }) } @@ -204,6 +205,7 @@ async fn get_cached_server_binary( Ok(( LanguageServerBinary { path: node.binary_path().await?, + env: None, arguments: vue_server_binary_arguments(&server_path), }, typescript_path, diff --git a/crates/zed/src/languages/yaml.rs b/crates/zed/src/languages/yaml.rs index 633e5d7da9..3020e13ef1 100644 --- a/crates/zed/src/languages/yaml.rs +++ b/crates/zed/src/languages/yaml.rs @@ -74,6 +74,7 @@ impl LspAdapter for YamlLspAdapter { Ok(LanguageServerBinary { path: self.node.binary_path().await?, + env: None, arguments: server_binary_arguments(&server_path), }) } @@ -124,6 +125,7 @@ async fn get_cached_server_binary( if server_path.exists() { Ok(LanguageServerBinary { path: node.binary_path().await?, + env: None, arguments: server_binary_arguments(&server_path), }) } else { diff --git a/crates/zed/src/languages/zig.rs b/crates/zed/src/languages/zig.rs index ee626d4b81..3c830fba4c 100644 --- a/crates/zed/src/languages/zig.rs +++ b/crates/zed/src/languages/zig.rs @@ -3,10 +3,13 @@ use async_compression::futures::bufread::GzipDecoder; use async_tar::Archive; use async_trait::async_trait; use futures::{io::BufReader, StreamExt}; +use gpui::{AsyncAppContext, Task}; use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; use lsp::LanguageServerBinary; use smol::fs; use std::env::consts::{ARCH, OS}; +use std::ffi::OsString; +use std::sync::Arc; use std::{any::Any, path::PathBuf}; use util::async_maybe; use util::github::latest_github_release; @@ -44,6 +47,25 @@ impl LspAdapter for ZlsAdapter { Ok(Box::new(version) as Box<_>) } + fn check_if_user_installed( + &self, + delegate: &Arc, + cx: &mut AsyncAppContext, + ) -> Option>> { + let delegate = delegate.clone(); + + Some(cx.spawn(|cx| async move { + match cx.update(|cx| delegate.which_command(OsString::from("zls"), cx)) { + Ok(task) => task.await.map(|(path, env)| LanguageServerBinary { + path, + arguments: vec![], + env: Some(env), + }), + Err(_) => None, + } + })) + } + async fn fetch_server_binary( &self, version: Box, @@ -75,6 +97,7 @@ impl LspAdapter for ZlsAdapter { } Ok(LanguageServerBinary { path: binary_path, + env: None, arguments: vec![], }) } @@ -119,6 +142,7 @@ async fn get_cached_server_binary(container_dir: PathBuf) -> Option