Detailed changes
@@ -1349,7 +1349,7 @@ dependencies = [
"rustc-hash",
"shlex",
"syn 2.0.48",
- "which",
+ "which 4.4.2",
]
[[package]]
@@ -4340,11 +4340,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]]
@@ -6915,6 +6915,7 @@ dependencies = [
"toml 0.8.10",
"unindent",
"util",
+ "which 6.0.0",
]
[[package]]
@@ -7024,7 +7025,7 @@ dependencies = [
"prost-types 0.9.0",
"regex",
"tempfile",
- "which",
+ "which 4.4.2",
]
[[package]]
@@ -11396,6 +11397,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"
@@ -278,6 +278,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]
@@ -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(
@@ -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<dyn LspAdapterDelegate>,
+ cx: &mut AsyncAppContext,
+ ) -> Option<Task<Option<LanguageServerBinary>>> {
+ 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<dyn HttpClient>;
+ fn which_command(
+ &self,
+ command: OsString,
+ cx: &AppContext,
+ ) -> Task<Option<(PathBuf, HashMap<String, String>)>>;
}
#[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<dyn LspAdapterDelegate>,
+ _: &mut AsyncAppContext,
+ ) -> Option<Task<Option<LanguageServerBinary>>> {
+ None
+ }
+
async fn fetch_latest_server_version(
&self,
delegate: &dyn LspAdapterDelegate,
@@ -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;
-
- 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 binary = match entry.await {
- Ok(binary) => binary,
- Err(err) => anyhow::bail!("{err}"),
+ // 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 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;
+
+ 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<CachedLspAdapter>,
+ language: Arc<Language>,
+ delegate: Arc<dyn LspAdapterDelegate>,
+ cx: &mut AsyncAppContext,
+) -> Option<LanguageServerBinary> {
+ 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<LanguageRegistry>,
+ adapter: &Arc<CachedLspAdapter>,
+ language: Arc<Language>,
+ delegate: &Arc<dyn LspAdapterDelegate>,
+ cx: &AsyncAppContext,
+ container_dir: Arc<Path>,
+ lsp_binary_statuses: LspBinaryStatusSender,
+) -> Result<LanguageServerBinary> {
+ 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<CachedLspAdapter>,
language: Arc<Language>,
@@ -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<LanguageServerBinary> {
let container_dir: Arc<Path> = 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?;
@@ -55,6 +55,7 @@ pub enum IoKind {
pub struct LanguageServerBinary {
pub path: PathBuf,
pub arguments: Vec<OsString>,
+ pub env: Option<HashMap<String, String>>,
}
/// 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())
@@ -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,
@@ -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"] }
@@ -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<Project>,
- http_client: Arc<dyn HttpClient>,
-}
-
// Currently, formatting operations are represented differently depending on
// whether they come from a language server or an external command.
enum FormatOperation {
@@ -2800,7 +2797,7 @@ impl Project {
fn start_language_server(
&mut self,
- worktree: &Model<Worktree>,
+ worktree_handle: &Model<Worktree>,
adapter: Arc<CachedLspAdapter>,
language: Arc<Language>,
cx: &mut ModelContext<Self>,
@@ -2809,7 +2806,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());
@@ -2823,7 +2820,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,
@@ -9271,10 +9268,17 @@ impl<P: AsRef<Path>> From<(WorktreeId, P)> for ProjectPath {
}
}
+struct ProjectLspAdapterDelegate {
+ project: Model<Project>,
+ worktree: Model<Worktree>,
+ http_client: Arc<dyn HttpClient>,
+}
+
impl ProjectLspAdapterDelegate {
- fn new(project: &Project, cx: &ModelContext<Project>) -> Arc<Self> {
+ fn new(project: &Project, worktree: &Model<Worktree>, cx: &ModelContext<Project>) -> Arc<Self> {
Arc::new(Self {
project: cx.handle(),
+ worktree: worktree.clone(),
http_client: project.client.http_client(),
})
}
@@ -9289,6 +9293,41 @@ impl LspAdapterDelegate for ProjectLspAdapterDelegate {
fn http_client(&self) -> Arc<dyn HttpClient> {
self.http_client.clone()
}
+
+ fn which_command(
+ &self,
+ command: OsString,
+ cx: &AppContext,
+ ) -> Task<Option<(PathBuf, HashMap<String, String>)>> {
+ 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 {
@@ -9396,3 +9435,55 @@ fn include_text(server: &lsp::LanguageServer) -> bool {
})
.unwrap_or(false)
}
+
+async fn load_shell_environment(dir: &Path) -> Result<HashMap<String, String>> {
+ 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)
+}
@@ -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 {
@@ -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<LanguageServ
if clangd_bin.exists() {
Ok(LanguageServerBinary {
path: clangd_bin,
+ env: None,
arguments: vec![],
})
} else {
@@ -105,6 +105,7 @@ impl super::LspAdapter for ClojureLspAdapter {
Ok(LanguageServerBinary {
path: binary_path,
+ env: None,
arguments: vec![],
})
}
@@ -118,6 +119,7 @@ impl super::LspAdapter for ClojureLspAdapter {
if binary_path.exists() {
Some(LanguageServerBinary {
path: binary_path,
+ env: None,
arguments: vec![],
})
} else {
@@ -133,6 +135,7 @@ impl super::LspAdapter for ClojureLspAdapter {
if binary_path.exists() {
Some(LanguageServerBinary {
path: binary_path,
+ env: None,
arguments: vec!["--version".into()],
})
} else {
@@ -92,6 +92,7 @@ impl super::LspAdapter for OmniSharpAdapter {
}
Ok(LanguageServerBinary {
path: binary_path,
+ env: None,
arguments: server_binary_arguments(),
})
}
@@ -136,6 +137,7 @@ async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServ
if let Some(path) = last_binary_path {
Ok(LanguageServerBinary {
path,
+ env: None,
arguments: server_binary_arguments(),
})
} else {
@@ -72,6 +72,7 @@ impl LspAdapter for CssLspAdapter {
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 {
@@ -39,6 +39,7 @@ impl LspAdapter for DartLanguageServer {
) -> Option<LanguageServerBinary> {
Some(LanguageServerBinary {
path: "dart".into(),
+ env: None,
arguments: vec!["language-server".into(), "--protocol=lsp".into()],
})
}
@@ -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<LanguageServ
if fs::metadata(&binary).await.is_ok() {
return Ok(LanguageServerBinary {
path: binary,
+ env: None,
arguments: deno_server_binary_arguments(),
});
}
@@ -71,6 +71,7 @@ impl LspAdapter for DockerfileLspAdapter {
Ok(LanguageServerBinary {
path: self.node.binary_path().await?,
+ env: None,
arguments: server_binary_arguments(&server_path),
})
}
@@ -110,6 +111,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 {
@@ -174,6 +174,7 @@ impl LspAdapter for ElixirLspAdapter {
Ok(LanguageServerBinary {
path: binary_path,
+ env: None,
arguments: vec![],
})
}
@@ -284,6 +285,7 @@ async fn get_cached_server_binary_elixir_ls(
if server_path.exists() {
Some(LanguageServerBinary {
path: server_path,
+ env: None,
arguments: vec![],
})
} else {
@@ -369,6 +371,7 @@ impl LspAdapter for NextLspAdapter {
Ok(LanguageServerBinary {
path: binary_path,
+ env: None,
arguments: vec!["--stdio".into()],
})
}
@@ -435,6 +438,7 @@ async fn get_cached_server_binary_next(container_dir: PathBuf) -> Option<Languag
if let Some(path) = last_binary_path {
Ok(LanguageServerBinary {
path,
+ env: None,
arguments: Vec::new(),
})
} else {
@@ -476,6 +480,7 @@ impl LspAdapter for LocalLspAdapter {
let path = shellexpand::full(&self.path)?;
Ok(LanguageServerBinary {
path: PathBuf::from(path.deref()),
+ env: None,
arguments: self.arguments.iter().map(|arg| arg.into()).collect(),
})
}
@@ -488,6 +493,7 @@ impl LspAdapter for LocalLspAdapter {
let path = shellexpand::full(&self.path).ok()?;
Some(LanguageServerBinary {
path: PathBuf::from(path.deref()),
+ env: None,
arguments: self.arguments.iter().map(|arg| arg.into()).collect(),
})
}
@@ -496,6 +502,7 @@ impl LspAdapter for LocalLspAdapter {
let path = shellexpand::full(&self.path).ok()?;
Some(LanguageServerBinary {
path: PathBuf::from(path.deref()),
+ env: None,
arguments: self.arguments.iter().map(|arg| arg.into()).collect(),
})
}
@@ -75,6 +75,7 @@ impl LspAdapter for ElmLspAdapter {
Ok(LanguageServerBinary {
path: self.node.binary_path().await?,
+ env: None,
arguments: server_binary_arguments(&server_path),
})
}
@@ -134,6 +135,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 {
@@ -41,6 +41,7 @@ impl LspAdapter for ErlangLspAdapter {
) -> Option<LanguageServerBinary> {
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<LanguageServerBinary> {
Some(LanguageServerBinary {
path: "erlang_ls".into(),
+ env: None,
arguments: vec!["--version".into()],
})
}
@@ -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<LanguageServ
anyhow::Ok(LanguageServerBinary {
path: last.ok_or_else(|| anyhow!("no cached binary"))?,
+ env: None,
arguments: server_binary_arguments(),
})
})
@@ -58,6 +58,25 @@ impl super::LspAdapter for GoLspAdapter {
Ok(Box::new(version) as Box<_>)
}
+ fn check_if_user_installed(
+ &self,
+ delegate: &Arc<dyn LspAdapterDelegate>,
+ cx: &mut AsyncAppContext,
+ ) -> Option<Task<Option<LanguageServerBinary>>> {
+ 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<dyn LspAdapterDelegate>,
@@ -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<LanguageServ
Ok(LanguageServerBinary {
path,
arguments: server_binary_arguments(),
+ env: None,
})
} else {
Err(anyhow!("no cached binary"))
@@ -41,6 +41,7 @@ impl LspAdapter for HaskellLanguageServer {
) -> Option<LanguageServerBinary> {
Some(LanguageServerBinary {
path: "haskell-language-server-wrapper".into(),
+ env: None,
arguments: vec!["lsp".into()],
})
}
@@ -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 {
@@ -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 {
@@ -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<LanguageServ
if let Some(path) = last_binary_path {
Ok(LanguageServerBinary {
path,
+ env: None,
arguments: Vec::new(),
})
} else {
@@ -41,6 +41,7 @@ impl LspAdapter for NuLanguageServer {
) -> Option<LanguageServerBinary> {
Some(LanguageServerBinary {
path: "nu".into(),
+ env: None,
arguments: vec!["--lsp".into()],
})
}
@@ -47,6 +47,7 @@ impl LspAdapter for OCamlLspAdapter {
) -> Option<LanguageServerBinary> {
Some(LanguageServerBinary {
path: "ocamllsp".into(),
+ env: None,
arguments: vec![],
})
}
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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 {
@@ -39,6 +39,7 @@ impl LspAdapter for RubyLanguageServer {
) -> Option<LanguageServerBinary> {
Some(LanguageServerBinary {
path: "solargraph".into(),
+ env: None,
arguments: vec!["stdio".into()],
})
}
@@ -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<LanguageServ
anyhow::Ok(LanguageServerBinary {
path: last.ok_or_else(|| anyhow!("no cached binary"))?,
+ env: None,
arguments: Default::default(),
})
})
@@ -71,6 +71,7 @@ impl LspAdapter for SvelteLspAdapter {
Ok(LanguageServerBinary {
path: self.node.binary_path().await?,
+ env: None,
arguments: server_binary_arguments(&server_path),
})
}
@@ -148,6 +149,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 {
@@ -73,6 +73,7 @@ impl LspAdapter for TailwindLspAdapter {
Ok(LanguageServerBinary {
path: self.node.binary_path().await?,
+ env: None,
arguments: server_binary_arguments(&server_path),
})
}
@@ -150,6 +151,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 {
@@ -85,6 +85,7 @@ impl LspAdapter for TaploLspAdapter {
Ok(LanguageServerBinary {
path: binary_path,
+ env: None,
arguments: vec!["lsp".into(), "stdio".into()],
})
}
@@ -120,6 +121,7 @@ async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServ
anyhow::Ok(LanguageServerBinary {
path: last.context("no cached binary")?,
+ env: None,
arguments: Default::default(),
})
})
@@ -97,6 +97,7 @@ impl LspAdapter for TypeScriptLspAdapter {
Ok(LanguageServerBinary {
path: self.node.binary_path().await?,
+ env: None,
arguments: typescript_server_binary_arguments(&server_path),
})
}
@@ -192,11 +193,13 @@ async fn get_cached_ts_server_binary(
if new_server_path.exists() {
Ok(LanguageServerBinary {
path: node.binary_path().await?,
+ env: None,
arguments: typescript_server_binary_arguments(&new_server_path),
})
} else if old_server_path.exists() {
Ok(LanguageServerBinary {
path: node.binary_path().await?,
+ env: None,
arguments: typescript_server_binary_arguments(&old_server_path),
})
} else {
@@ -307,6 +310,7 @@ impl LspAdapter for EsLintLspAdapter {
Ok(LanguageServerBinary {
path: self.node.binary_path().await?,
+ env: None,
arguments: eslint_server_binary_arguments(&server_path),
})
}
@@ -354,6 +358,7 @@ async fn get_cached_eslint_server_binary(
Ok(LanguageServerBinary {
path: node.binary_path().await?,
+ env: None,
arguments: eslint_server_binary_arguments(&server_path),
})
})
@@ -41,6 +41,7 @@ impl LspAdapter for UiuaLanguageServer {
) -> Option<LanguageServerBinary> {
Some(LanguageServerBinary {
path: "uiua".into(),
+ env: None,
arguments: vec!["lsp".into()],
})
}
@@ -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,
@@ -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 {
@@ -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<dyn LspAdapterDelegate>,
+ cx: &mut AsyncAppContext,
+ ) -> Option<Task<Option<LanguageServerBinary>>> {
+ 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<dyn 'static + Send + Any>,
@@ -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<LanguageServ
if let Some(path) = last_binary_path {
Ok(LanguageServerBinary {
path,
+ env: None,
arguments: Vec::new(),
})
} else {