From b57921186113f15c812743ff7eddc931b6cfbd9c Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 21 Mar 2023 14:39:20 -0400 Subject: [PATCH 1/9] Report if language server requires Node or not --- crates/language/src/language.rs | 12 ++++++++++++ crates/zed/src/languages/c.rs | 4 ++++ crates/zed/src/languages/elixir.rs | 4 ++++ crates/zed/src/languages/go.rs | 4 ++++ crates/zed/src/languages/html.rs | 6 +++++- crates/zed/src/languages/json.rs | 6 +++++- crates/zed/src/languages/language_plugin.rs | 13 ++++++++++++- crates/zed/src/languages/lua.rs | 6 +++++- crates/zed/src/languages/python.rs | 6 +++++- crates/zed/src/languages/ruby.rs | 6 +++++- crates/zed/src/languages/rust.rs | 4 ++++ crates/zed/src/languages/typescript.rs | 6 +++++- crates/zed/src/languages/yaml.rs | 6 +++++- 13 files changed, 75 insertions(+), 8 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index ed02b8de59ec2fa85e5e09571c5d9d423eaed513..201eb8c7fc3d955e79bcc90d29e778198718ba96 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -77,6 +77,12 @@ pub trait ToLspPosition { #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct LanguageServerName(pub Arc); +#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)] +pub enum ServerExecutionKind { + Launch, + Node, +} + /// Represents a Language Server, with certain cached sync properties. /// Uses [`LspAdapter`] under the hood, but calls all 'static' methods /// once at startup, and caches the results. @@ -172,6 +178,8 @@ impl CachedLspAdapter { pub trait LspAdapter: 'static + Send + Sync { async fn name(&self) -> LanguageServerName; + async fn server_execution_kind(&self) -> ServerExecutionKind; + async fn fetch_latest_server_version( &self, http: Arc, @@ -1442,6 +1450,10 @@ impl LspAdapter for Arc { LanguageServerName(self.name.into()) } + async fn server_execution_kind(&self) -> ServerExecutionKind { + ServerExecutionKind::Launch + } + async fn fetch_latest_server_version( &self, _: Arc, diff --git a/crates/zed/src/languages/c.rs b/crates/zed/src/languages/c.rs index 9fbb12857f74c14745d08253d1707327381fbf36..826de55874248556a84641a8985591ddb5aa7cd7 100644 --- a/crates/zed/src/languages/c.rs +++ b/crates/zed/src/languages/c.rs @@ -16,6 +16,10 @@ impl super::LspAdapter for CLspAdapter { LanguageServerName("clangd".into()) } + async fn server_execution_kind(&self) -> ServerExecutionKind { + ServerExecutionKind::Launch + } + async fn fetch_latest_server_version( &self, http: Arc, diff --git a/crates/zed/src/languages/elixir.rs b/crates/zed/src/languages/elixir.rs index 75b35bb630b80379c6654f30b9cbb36e885f1594..0bdbaddd681604257bf4f6b24378070a6ff69d68 100644 --- a/crates/zed/src/languages/elixir.rs +++ b/crates/zed/src/languages/elixir.rs @@ -17,6 +17,10 @@ impl LspAdapter for ElixirLspAdapter { LanguageServerName("elixir-ls".into()) } + async fn server_execution_kind(&self) -> ServerExecutionKind { + ServerExecutionKind::Launch + } + async fn fetch_latest_server_version( &self, http: Arc, diff --git a/crates/zed/src/languages/go.rs b/crates/zed/src/languages/go.rs index dd819338d0bf9a3a36566c97b72dd248a90a8e6d..4e34b126a1d7fb41c45258bb987f2e8705144b2e 100644 --- a/crates/zed/src/languages/go.rs +++ b/crates/zed/src/languages/go.rs @@ -23,6 +23,10 @@ impl super::LspAdapter for GoLspAdapter { LanguageServerName("gopls".into()) } + async fn server_execution_kind(&self) -> ServerExecutionKind { + ServerExecutionKind::Launch + } + async fn server_args(&self) -> Vec { vec!["-mode=stdio".into()] } diff --git a/crates/zed/src/languages/html.rs b/crates/zed/src/languages/html.rs index 5497841d886939a3e55a5dbe2a12ea6f07045f87..2fcf3b26997f359190a62cd5df1bd3900da379a5 100644 --- a/crates/zed/src/languages/html.rs +++ b/crates/zed/src/languages/html.rs @@ -3,7 +3,7 @@ use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use client::http::HttpClient; use futures::StreamExt; -use language::{LanguageServerName, LspAdapter}; +use language::{LanguageServerName, LspAdapter, ServerExecutionKind}; use serde_json::json; use smol::fs; use std::{any::Any, path::PathBuf, sync::Arc}; @@ -22,6 +22,10 @@ impl LspAdapter for HtmlLspAdapter { LanguageServerName("vscode-html-language-server".into()) } + async fn server_execution_kind(&self) -> ServerExecutionKind { + ServerExecutionKind::Node + } + async fn server_args(&self) -> Vec { vec!["--stdio".into()] } diff --git a/crates/zed/src/languages/json.rs b/crates/zed/src/languages/json.rs index d5c59d28d119b35ac6b23bbed9c384f303d04e08..9df5bec6c0572e9c4151561b5d64b6e123eb16f3 100644 --- a/crates/zed/src/languages/json.rs +++ b/crates/zed/src/languages/json.rs @@ -6,7 +6,7 @@ use client::http::HttpClient; use collections::HashMap; use futures::{future::BoxFuture, io::BufReader, FutureExt, StreamExt}; use gpui::MutableAppContext; -use language::{LanguageRegistry, LanguageServerName, LspAdapter}; +use language::{LanguageRegistry, LanguageServerName, LspAdapter, ServerExecutionKind}; use serde_json::json; use settings::{keymap_file_json_schema, settings_file_json_schema}; use smol::fs::{self, File}; @@ -37,6 +37,10 @@ impl LspAdapter for JsonLspAdapter { LanguageServerName("json-language-server".into()) } + async fn server_execution_kind(&self) -> ServerExecutionKind { + ServerExecutionKind::Node + } + async fn server_args(&self) -> Vec { vec!["--stdio".into()] } diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index bd9a9d005fd7643ab880786f92de2055a69a9380..de3cabcef8f75160fe4b77e18ade83921c6a493e 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -4,7 +4,7 @@ use client::http::HttpClient; use collections::HashMap; use futures::lock::Mutex; use gpui::executor::Background; -use language::{LanguageServerName, LspAdapter}; +use language::{LanguageServerName, LspAdapter, ServerExecutionKind}; use plugin_runtime::{Plugin, PluginBinary, PluginBuilder, WasiFn}; use std::{any::Any, path::PathBuf, sync::Arc}; use util::ResultExt; @@ -32,6 +32,7 @@ pub async fn new_json(executor: Arc) -> Result { pub struct PluginLspAdapter { name: WasiFn<(), String>, + server_execution_kind: WasiFn<(), ServerExecutionKind>, server_args: WasiFn<(), Vec>, fetch_latest_server_version: WasiFn<(), Option>, fetch_server_binary: WasiFn<(PathBuf, String), Result>, @@ -47,6 +48,7 @@ impl PluginLspAdapter { pub async fn new(mut plugin: Plugin, executor: Arc) -> Result { Ok(Self { name: plugin.function("name")?, + server_execution_kind: plugin.function("server_execution_kind")?, server_args: plugin.function("server_args")?, fetch_latest_server_version: plugin.function("fetch_latest_server_version")?, fetch_server_binary: plugin.function("fetch_server_binary")?, @@ -72,6 +74,15 @@ impl LspAdapter for PluginLspAdapter { LanguageServerName(name.into()) } + async fn server_execution_kind(&self) -> ServerExecutionKind { + self.runtime + .lock() + .await + .call(&self.server_execution_kind, ()) + .await + .unwrap() + } + async fn server_args<'a>(&'a self) -> Vec { self.runtime .lock() diff --git a/crates/zed/src/languages/lua.rs b/crates/zed/src/languages/lua.rs index 4bcffca9084257bd7595b8c19735418cc2b8d65b..c51235e1f172a3c99c216f47ccf1125b4261e832 100644 --- a/crates/zed/src/languages/lua.rs +++ b/crates/zed/src/languages/lua.rs @@ -6,7 +6,7 @@ use async_tar::Archive; use async_trait::async_trait; use client::http::HttpClient; use futures::{io::BufReader, StreamExt}; -use language::LanguageServerName; +use language::{LanguageServerName, ServerExecutionKind}; use smol::fs; use util::{async_iife, ResultExt}; @@ -21,6 +21,10 @@ impl super::LspAdapter for LuaLspAdapter { LanguageServerName("lua-language-server".into()) } + async fn server_execution_kind(&self) -> ServerExecutionKind { + ServerExecutionKind::Launch + } + async fn server_args(&self) -> Vec { vec![ "--logpath=~/lua-language-server.log".into(), diff --git a/crates/zed/src/languages/python.rs b/crates/zed/src/languages/python.rs index 1391494ab1ea1e324caf9da769c34b5962121679..a980e2ad75b0a8bf238b7fdbacc9ffaf134c8d55 100644 --- a/crates/zed/src/languages/python.rs +++ b/crates/zed/src/languages/python.rs @@ -3,7 +3,7 @@ use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use client::http::HttpClient; use futures::StreamExt; -use language::{LanguageServerName, LspAdapter}; +use language::{LanguageServerName, LspAdapter, ServerExecutionKind}; use smol::fs; use std::{any::Any, path::PathBuf, sync::Arc}; use util::ResultExt; @@ -20,6 +20,10 @@ impl LspAdapter for PythonLspAdapter { LanguageServerName("pyright".into()) } + async fn server_execution_kind(&self) -> ServerExecutionKind { + ServerExecutionKind::Node + } + async fn server_args(&self) -> Vec { vec!["--stdio".into()] } diff --git a/crates/zed/src/languages/ruby.rs b/crates/zed/src/languages/ruby.rs index cbfb5e35a70f06da84c5fa551845bb63385e3f94..1308610a5a9223b9070a786f904c95affe4c5f3b 100644 --- a/crates/zed/src/languages/ruby.rs +++ b/crates/zed/src/languages/ruby.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use client::http::HttpClient; -use language::{LanguageServerName, LspAdapter}; +use language::{LanguageServerName, LspAdapter, ServerExecutionKind}; use std::{any::Any, path::PathBuf, sync::Arc}; pub struct RubyLanguageServer; @@ -12,6 +12,10 @@ impl LspAdapter for RubyLanguageServer { LanguageServerName("solargraph".into()) } + async fn server_execution_kind(&self) -> ServerExecutionKind { + ServerExecutionKind::Launch + } + async fn server_args(&self) -> Vec { vec!["stdio".into()] } diff --git a/crates/zed/src/languages/rust.rs b/crates/zed/src/languages/rust.rs index a8f7fcbc4dcf34ff3e017be53cfd967a0e185dfe..1f9940ac0d45bab21996590edbf4c76be7026e54 100644 --- a/crates/zed/src/languages/rust.rs +++ b/crates/zed/src/languages/rust.rs @@ -19,6 +19,10 @@ impl LspAdapter for RustLspAdapter { LanguageServerName("rust-analyzer".into()) } + async fn server_execution_kind(&self) -> ServerExecutionKind { + ServerExecutionKind::Launch + } + async fn fetch_latest_server_version( &self, http: Arc, diff --git a/crates/zed/src/languages/typescript.rs b/crates/zed/src/languages/typescript.rs index 5290158deaf037b1a9ddb8a52211871c58a2a815..3f2e25d9e62280807804626b918cc4d01fa43381 100644 --- a/crates/zed/src/languages/typescript.rs +++ b/crates/zed/src/languages/typescript.rs @@ -3,7 +3,7 @@ use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use client::http::HttpClient; use futures::StreamExt; -use language::{LanguageServerName, LspAdapter}; +use language::{LanguageServerName, LspAdapter, ServerExecutionKind}; use serde_json::json; use smol::fs; use std::{any::Any, path::PathBuf, sync::Arc}; @@ -27,6 +27,10 @@ impl LspAdapter for TypeScriptLspAdapter { LanguageServerName("typescript-language-server".into()) } + async fn server_execution_kind(&self) -> ServerExecutionKind { + ServerExecutionKind::Node + } + async fn server_args(&self) -> Vec { ["--stdio", "--tsserver-path", "node_modules/typescript/lib"] .into_iter() diff --git a/crates/zed/src/languages/yaml.rs b/crates/zed/src/languages/yaml.rs index 9750ecce88d6f5ea73824bb801d9ac73764d69d9..c179ec5b2da78d3aff92398504e7b64ec2b10483 100644 --- a/crates/zed/src/languages/yaml.rs +++ b/crates/zed/src/languages/yaml.rs @@ -3,7 +3,7 @@ use async_trait::async_trait; use client::http::HttpClient; use futures::{future::BoxFuture, FutureExt, StreamExt}; use gpui::MutableAppContext; -use language::{LanguageServerName, LspAdapter}; +use language::{LanguageServerName, LspAdapter, ServerExecutionKind}; use serde_json::Value; use settings::Settings; use smol::fs; @@ -24,6 +24,10 @@ impl LspAdapter for YamlLspAdapter { LanguageServerName("yaml-language-server".into()) } + async fn server_execution_kind(&self) -> ServerExecutionKind { + ServerExecutionKind::Node + } + async fn server_args(&self) -> Vec { vec!["--stdio".into()] } From edd6c85af7244be5210166be9b3b6d24dee064fc Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 23 Mar 2023 17:32:13 -0400 Subject: [PATCH 2/9] Initial running of servers on downloaded Node --- crates/collab/src/tests.rs | 2 +- crates/language/src/language.rs | 41 ++++++++++++++++------ crates/lsp/src/lsp.rs | 20 +++++------ crates/util/src/paths.rs | 1 + crates/zed/src/languages.rs | 2 ++ crates/zed/src/languages/installation.rs | 44 ++++++++++++++++++++++-- crates/zed/src/main.rs | 11 +++++- crates/zed/src/zed.rs | 2 +- 8 files changed, 97 insertions(+), 26 deletions(-) diff --git a/crates/collab/src/tests.rs b/crates/collab/src/tests.rs index 6ebfdc90b72f81ba81fa4f3e8058ef63c24930de..b31feaa9b0b5069cb621fdd91bd9bfd44f8811ef 100644 --- a/crates/collab/src/tests.rs +++ b/crates/collab/src/tests.rs @@ -188,7 +188,7 @@ impl TestServer { let app_state = Arc::new(workspace::AppState { client: client.clone(), user_store: user_store.clone(), - languages: Arc::new(LanguageRegistry::new(Task::ready(()))), + languages: Arc::new(LanguageRegistry::test()), themes: ThemeRegistry::new((), cx.font_cache()), fs: fs.clone(), build_window_options: |_, _, _| Default::default(), diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 201eb8c7fc3d955e79bcc90d29e778198718ba96..c62bbbb146d2734e334d19dfcfd304e8a4b1d5e5 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -492,6 +492,7 @@ pub struct LanguageRegistry { lsp_binary_statuses_tx: async_broadcast::Sender<(Arc, LanguageServerBinaryStatus)>, lsp_binary_statuses_rx: async_broadcast::Receiver<(Arc, LanguageServerBinaryStatus)>, login_shell_env_loaded: Shared>, + node_path: Shared>>, #[allow(clippy::type_complexity)] lsp_binary_paths: Mutex< HashMap< @@ -513,7 +514,7 @@ struct LanguageRegistryState { } impl LanguageRegistry { - pub fn new(login_shell_env_loaded: Task<()>) -> Self { + pub fn new(login_shell_env_loaded: Task<()>, node_path: Task>) -> Self { let (lsp_binary_statuses_tx, lsp_binary_statuses_rx) = async_broadcast::broadcast(16); Self { state: RwLock::new(LanguageRegistryState { @@ -529,6 +530,7 @@ impl LanguageRegistry { lsp_binary_statuses_tx, lsp_binary_statuses_rx, login_shell_env_loaded: login_shell_env_loaded.shared(), + node_path: node_path.shared(), lsp_binary_paths: Default::default(), executor: None, } @@ -536,7 +538,7 @@ impl LanguageRegistry { #[cfg(any(test, feature = "test-support"))] pub fn test() -> Self { - Self::new(Task::ready(())) + Self::new(Task::ready(()), Task::Ready(None)) } pub fn set_executor(&mut self, executor: Arc) { @@ -802,8 +804,12 @@ impl LanguageRegistry { let adapter = language.adapter.clone()?; let lsp_binary_statuses = self.lsp_binary_statuses_tx.clone(); let login_shell_env_loaded = self.login_shell_env_loaded.clone(); + let node_path = self.node_path.clone(); + Some(cx.spawn(|cx| async move { login_shell_env_loaded.await; + let node_path = node_path.await; + let server_binary_path = this .lsp_binary_paths .lock() @@ -824,14 +830,27 @@ impl LanguageRegistry { .map_err(|e| anyhow!(e)); let server_binary_path = server_binary_path.await?; - let server_args = &adapter.server_args; - let server = lsp::LanguageServer::new( - server_id, - &server_binary_path, - server_args, - &root_path, - cx, - )?; + let server_name = server_binary_path + .file_name() + .map(|name| name.to_string_lossy().to_string()); + + let mut command = match adapter.adapter.server_execution_kind().await { + ServerExecutionKind::Node => { + let node_path = node_path + .ok_or(anyhow!("Missing Node path for Node based language server"))?; + let node_binary = node_path.join("bin/node"); + dbg!(&node_binary); + let mut command = smol::process::Command::new(node_binary); + command.arg(dbg!(server_binary_path)); + command + } + + ServerExecutionKind::Launch => smol::process::Command::new(server_binary_path), + }; + + command.args(&adapter.server_args); + let server = lsp::LanguageServer::new(server_id, server_name, command, &root_path, cx)?; + Ok(server) })) } @@ -1528,7 +1547,7 @@ mod tests { #[gpui::test(iterations = 10)] async fn test_language_loading(cx: &mut TestAppContext) { - let mut languages = LanguageRegistry::new(Task::ready(())); + let mut languages = LanguageRegistry::test(); languages.set_executor(cx.background()); let languages = Arc::new(languages); languages.register( diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index ad94423e11ad3db1136c2ee1c07b3766690ebd8b..87dbf95b11835e9c432b5ac4fd471330f9738b29 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -105,10 +105,10 @@ struct Error { } impl LanguageServer { - pub fn new>( + pub fn new( server_id: usize, - binary_path: &Path, - args: &[T], + server_name: Option, + mut command: process::Command, root_path: &Path, cx: AsyncAppContext, ) -> Result { @@ -117,18 +117,17 @@ impl LanguageServer { } else { root_path.parent().unwrap_or_else(|| Path::new("/")) }; - let mut server = process::Command::new(binary_path) + + let mut server = dbg!(command .current_dir(working_dir) - .args(args) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::inherit()) - .kill_on_drop(true) - .spawn()?; + .kill_on_drop(true)) + .spawn()?; let stdin = server.stdin.take().unwrap(); let stout = server.stdout.take().unwrap(); - let mut server = Self::new_internal( server_id, stdin, @@ -147,8 +146,9 @@ impl LanguageServer { ); }, ); - if let Some(name) = binary_path.file_name() { - server.name = name.to_string_lossy().to_string(); + + if let Some(name) = server_name { + server.name = name; } Ok(server) } diff --git a/crates/util/src/paths.rs b/crates/util/src/paths.rs index 8698d6891e18e7e3e960a210ec7b3f23e308da72..63c3c6d884a4e91fdeaff3766adc97a99072ec24 100644 --- a/crates/util/src/paths.rs +++ b/crates/util/src/paths.rs @@ -4,6 +4,7 @@ lazy_static::lazy_static! { pub static ref HOME: PathBuf = dirs::home_dir().expect("failed to determine home directory"); pub static ref CONFIG_DIR: PathBuf = HOME.join(".config").join("zed"); pub static ref LOGS_DIR: PathBuf = HOME.join("Library/Logs/Zed"); + pub static ref SUPPORT_DIR: PathBuf = HOME.join("Library/Application Support/Zed"); pub static ref LANGUAGES_DIR: PathBuf = HOME.join("Library/Application Support/Zed/languages"); pub static ref DB_DIR: PathBuf = HOME.join("Library/Application Support/Zed/db"); pub static ref SETTINGS: PathBuf = CONFIG_DIR.join("settings.json"); diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 1acee4bad4d09814a656bd21e6536278bd62889a..6bf1d94c77051f38b4fc5414b833bd3ba7f1b66c 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -18,6 +18,8 @@ mod rust; mod typescript; mod yaml; +pub use installation::ensure_node_installation_dir; + // 1. Add tree-sitter-{language} parser to zed crate // 2. Create a language directory in zed/crates/zed/src/languages and add the language to init function below // 3. Add config.toml to the newly created language directory using existing languages as a template diff --git a/crates/zed/src/languages/installation.rs b/crates/zed/src/languages/installation.rs index c5aff17e566b69b33e788ded686fd2eb8acef1d3..54fd6ddff95bdf9503c4f5feea8291acdad5e1c8 100644 --- a/crates/zed/src/languages/installation.rs +++ b/crates/zed/src/languages/installation.rs @@ -1,9 +1,15 @@ use anyhow::{anyhow, Context, Result}; +use async_compression::futures::bufread::GzipDecoder; +use async_tar::Archive; use client::http::HttpClient; - +use futures::{io::BufReader, StreamExt}; use serde::Deserialize; +use smol::fs::{self, File}; use smol::io::AsyncReadExt; -use std::{path::Path, sync::Arc}; +use std::{ + path::{Path, PathBuf}, + sync::Arc, +}; pub struct GitHubLspBinaryVersion { pub name: String, @@ -35,6 +41,40 @@ pub(crate) struct GithubReleaseAsset { pub browser_download_url: String, } +pub async fn ensure_node_installation_dir(http: Arc) -> Result { + eprintln!("ensure_node_installation_dir"); + + let version = "v18.15.0"; + let arch = "arm64"; + + let folder_name = format!("node-{version}-darwin-{arch}"); + let node_containing_dir = dbg!(util::paths::SUPPORT_DIR.join("node")); + let node_dir = dbg!(node_containing_dir.join(folder_name)); + let node_binary = node_dir.join("bin/node"); + + if fs::metadata(&node_binary).await.is_err() { + _ = fs::remove_dir_all(&node_containing_dir).await; + fs::create_dir(&node_containing_dir) + .await + .context("error creating node containing dir")?; + + let url = format!("https://nodejs.org/dist/{version}/node-{version}-darwin-{arch}.tar.gz"); + dbg!(&url); + let mut response = http + .get(&url, Default::default(), true) + .await + .context("error downloading Node binary tarball")?; + + let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut())); + let archive = Archive::new(decompressed_bytes); + archive.unpack(&node_containing_dir).await?; + eprintln!("unpacked"); + } + + eprintln!("returning"); + Ok(dbg!(node_dir)) +} + pub async fn npm_package_latest_version(name: &str) -> Result { let output = smol::process::Command::new("npm") .args(["-fetch-retry-mintimeout", "2000"]) diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index b1813bab5000405a5db428304397d7e7cb115f30..d794e4c39d307365be25ebaf42c7d7f690a3b085 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -81,6 +81,15 @@ fn main() { }) }; + let node_path = { + let http = http.clone(); + app.background().spawn(async move { + languages::ensure_node_installation_dir(http.clone()) + .await + .log_err() + }) + }; + let (cli_connections_tx, mut cli_connections_rx) = mpsc::unbounded(); let (open_paths_tx, mut open_paths_rx) = mpsc::unbounded(); app.on_open_urls(move |urls, _| { @@ -135,7 +144,7 @@ fn main() { } let client = client::Client::new(http.clone(), cx); - let mut languages = LanguageRegistry::new(login_shell_env_loaded); + let mut languages = LanguageRegistry::new(login_shell_env_loaded, node_path); languages.set_executor(cx.background().clone()); languages.set_language_server_download_dir(paths::LANGUAGES_DIR.clone()); let languages = Arc::new(languages); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index b0239d234c8a30476ee915bbec4f5fec04e3faa1..a28b407328f001530c708e10cdc9b5bb36dc107d 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -1846,7 +1846,7 @@ mod tests { #[gpui::test] fn test_bundled_languages(cx: &mut MutableAppContext) { - let mut languages = LanguageRegistry::new(Task::ready(())); + let mut languages = LanguageRegistry::test(); languages.set_executor(cx.background().clone()); let languages = Arc::new(languages); let themes = ThemeRegistry::new((), cx.font_cache().clone()); From ed442cfc8c48ee5bfdc43d17d0237ed6c8326b5b Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 23 Mar 2023 18:31:46 -0400 Subject: [PATCH 3/9] Invoke npm from downloaded Node --- crates/collab/src/tests.rs | 4 +--- crates/zed/src/languages/html.rs | 10 +++++++--- crates/zed/src/languages/installation.rs | 23 ++++++++++++++++++----- crates/zed/src/languages/python.rs | 8 ++++---- crates/zed/src/languages/typescript.rs | 9 +++++---- crates/zed/src/languages/yaml.rs | 14 +++++++++----- crates/zed/src/zed.rs | 2 +- 7 files changed, 45 insertions(+), 25 deletions(-) diff --git a/crates/collab/src/tests.rs b/crates/collab/src/tests.rs index b31feaa9b0b5069cb621fdd91bd9bfd44f8811ef..91af40dc5a688077422d4e892aad59b5ad237c10 100644 --- a/crates/collab/src/tests.rs +++ b/crates/collab/src/tests.rs @@ -13,9 +13,7 @@ use client::{ use collections::{HashMap, HashSet}; use fs::FakeFs; use futures::{channel::oneshot, StreamExt as _}; -use gpui::{ - executor::Deterministic, test::EmptyView, ModelHandle, Task, TestAppContext, ViewHandle, -}; +use gpui::{executor::Deterministic, test::EmptyView, ModelHandle, TestAppContext, ViewHandle}; use language::LanguageRegistry; use parking_lot::Mutex; use project::{Project, WorktreeId}; diff --git a/crates/zed/src/languages/html.rs b/crates/zed/src/languages/html.rs index 2fcf3b26997f359190a62cd5df1bd3900da379a5..f7ade20dfbaf9eb2d4c0662f67216c5cb692634d 100644 --- a/crates/zed/src/languages/html.rs +++ b/crates/zed/src/languages/html.rs @@ -32,15 +32,18 @@ impl LspAdapter for HtmlLspAdapter { async fn fetch_latest_server_version( &self, - _: Arc, + http: Arc, ) -> Result> { - Ok(Box::new(npm_package_latest_version("vscode-langservers-extracted").await?) as Box<_>) + Ok( + Box::new(npm_package_latest_version(http, "vscode-langservers-extracted").await?) + as Box<_>, + ) } async fn fetch_server_binary( &self, version: Box, - _: Arc, + http: Arc, container_dir: PathBuf, ) -> Result { let version = version.downcast::().unwrap(); @@ -52,6 +55,7 @@ impl LspAdapter for HtmlLspAdapter { if fs::metadata(&binary_path).await.is_err() { npm_install_packages( + http, [("vscode-langservers-extracted", version.as_str())], &version_dir, ) diff --git a/crates/zed/src/languages/installation.rs b/crates/zed/src/languages/installation.rs index 54fd6ddff95bdf9503c4f5feea8291acdad5e1c8..852b8f0de0b9f47083e7dfbc97939c4abc231922 100644 --- a/crates/zed/src/languages/installation.rs +++ b/crates/zed/src/languages/installation.rs @@ -2,9 +2,9 @@ use anyhow::{anyhow, Context, Result}; use async_compression::futures::bufread::GzipDecoder; use async_tar::Archive; use client::http::HttpClient; -use futures::{io::BufReader, StreamExt}; +use futures::io::BufReader; use serde::Deserialize; -use smol::fs::{self, File}; +use smol::fs::{self}; use smol::io::AsyncReadExt; use std::{ path::{Path, PathBuf}, @@ -75,10 +75,16 @@ pub async fn ensure_node_installation_dir(http: Arc) -> Result

Result { - let output = smol::process::Command::new("npm") +pub async fn npm_package_latest_version(http: Arc, name: &str) -> Result { + let node_dir = ensure_node_installation_dir(http).await?; + let node_binary = node_dir.join("bin/npm"); + let npm_file = node_dir.join("bin/npm"); + + let output = smol::process::Command::new(node_binary) + .arg(npm_file) .args(["-fetch-retry-mintimeout", "2000"]) .args(["-fetch-retry-maxtimeout", "5000"]) + .args(["-fetch-timeout", "5000"]) .args(["info", name, "--json"]) .output() .await @@ -98,12 +104,19 @@ pub async fn npm_package_latest_version(name: &str) -> Result { } pub async fn npm_install_packages( + http: Arc, packages: impl IntoIterator, directory: &Path, ) -> Result<()> { - let output = smol::process::Command::new("npm") + let node_dir = ensure_node_installation_dir(http).await?; + let node_binary = node_dir.join("bin/npm"); + let npm_file = node_dir.join("bin/npm"); + + let output = smol::process::Command::new(node_binary) + .arg(npm_file) .args(["-fetch-retry-mintimeout", "2000"]) .args(["-fetch-retry-maxtimeout", "5000"]) + .args(["-fetch-timeout", "5000"]) .arg("install") .arg("--prefix") .arg(directory) diff --git a/crates/zed/src/languages/python.rs b/crates/zed/src/languages/python.rs index a980e2ad75b0a8bf238b7fdbacc9ffaf134c8d55..4495b9e4d8e9ae15e4c16e3326d51ae9a6110f9c 100644 --- a/crates/zed/src/languages/python.rs +++ b/crates/zed/src/languages/python.rs @@ -30,15 +30,15 @@ impl LspAdapter for PythonLspAdapter { async fn fetch_latest_server_version( &self, - _: Arc, + http: Arc, ) -> Result> { - Ok(Box::new(npm_package_latest_version("pyright").await?) as Box<_>) + Ok(Box::new(npm_package_latest_version(http, "pyright").await?) as Box<_>) } async fn fetch_server_binary( &self, version: Box, - _: Arc, + http: Arc, container_dir: PathBuf, ) -> Result { let version = version.downcast::().unwrap(); @@ -49,7 +49,7 @@ impl LspAdapter for PythonLspAdapter { let binary_path = version_dir.join(Self::BIN_PATH); if fs::metadata(&binary_path).await.is_err() { - npm_install_packages([("pyright", version.as_str())], &version_dir).await?; + npm_install_packages(http, [("pyright", version.as_str())], &version_dir).await?; if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() { while let Some(entry) = entries.next().await { diff --git a/crates/zed/src/languages/typescript.rs b/crates/zed/src/languages/typescript.rs index 3f2e25d9e62280807804626b918cc4d01fa43381..7775da6067045bcd0feb77193da8e1809d36e3aa 100644 --- a/crates/zed/src/languages/typescript.rs +++ b/crates/zed/src/languages/typescript.rs @@ -40,18 +40,18 @@ impl LspAdapter for TypeScriptLspAdapter { async fn fetch_latest_server_version( &self, - _: Arc, + http: Arc, ) -> Result> { Ok(Box::new(Versions { - typescript_version: npm_package_latest_version("typescript").await?, - server_version: npm_package_latest_version("typescript-language-server").await?, + typescript_version: npm_package_latest_version(http.clone(), "typescript").await?, + server_version: npm_package_latest_version(http, "typescript-language-server").await?, }) as Box<_>) } async fn fetch_server_binary( &self, versions: Box, - _: Arc, + http: Arc, container_dir: PathBuf, ) -> Result { let versions = versions.downcast::().unwrap(); @@ -66,6 +66,7 @@ impl LspAdapter for TypeScriptLspAdapter { if fs::metadata(&binary_path).await.is_err() { npm_install_packages( + http, [ ("typescript", versions.typescript_version.as_str()), ( diff --git a/crates/zed/src/languages/yaml.rs b/crates/zed/src/languages/yaml.rs index c179ec5b2da78d3aff92398504e7b64ec2b10483..da03e6d5cfd215df5747203fc1f2564fb69468a4 100644 --- a/crates/zed/src/languages/yaml.rs +++ b/crates/zed/src/languages/yaml.rs @@ -34,15 +34,15 @@ impl LspAdapter for YamlLspAdapter { async fn fetch_latest_server_version( &self, - _: Arc, + http: Arc, ) -> Result> { - Ok(Box::new(npm_package_latest_version("yaml-language-server").await?) as Box<_>) + Ok(Box::new(npm_package_latest_version(http, "yaml-language-server").await?) as Box<_>) } async fn fetch_server_binary( &self, version: Box, - _: Arc, + http: Arc, container_dir: PathBuf, ) -> Result { let version = version.downcast::().unwrap(); @@ -53,8 +53,12 @@ impl LspAdapter for YamlLspAdapter { let binary_path = version_dir.join(Self::BIN_PATH); if fs::metadata(&binary_path).await.is_err() { - npm_install_packages([("yaml-language-server", version.as_str())], &version_dir) - .await?; + npm_install_packages( + http, + [("yaml-language-server", version.as_str())], + &version_dir, + ) + .await?; if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() { while let Some(entry) = entries.next().await { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index a28b407328f001530c708e10cdc9b5bb36dc107d..fd6b56027279be1b4579551c4cfbbe6375afa3fb 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -654,7 +654,7 @@ mod tests { use assets::Assets; use editor::{scroll::autoscroll::Autoscroll, DisplayPoint, Editor}; use gpui::{ - executor::Deterministic, AssetSource, MutableAppContext, Task, TestAppContext, ViewHandle, + executor::Deterministic, AssetSource, MutableAppContext, TestAppContext, ViewHandle, }; use language::LanguageRegistry; use project::{Project, ProjectPath}; From 1a2e509e3544c56cdc542e66e2355c8f120a088d Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 24 Mar 2023 10:40:50 -0400 Subject: [PATCH 4/9] Remove `server_args` from `LspAdapter` Prepare to remove concept of a runtime from greater server startup code, which is important for future language server extensibility Co-Authored-By: Antonio Scandurra --- crates/language/src/language.rs | 68 +++++++++++---------- crates/zed/src/languages/c.rs | 14 +++-- crates/zed/src/languages/elixir.rs | 15 +++-- crates/zed/src/languages/go.rs | 27 +++++--- crates/zed/src/languages/html.rs | 24 +++++--- crates/zed/src/languages/installation.rs | 8 +-- crates/zed/src/languages/json.rs | 26 +++++--- crates/zed/src/languages/language_plugin.rs | 26 +++----- crates/zed/src/languages/lua.rs | 30 +++++---- crates/zed/src/languages/python.rs | 24 +++++--- crates/zed/src/languages/ruby.rs | 15 +++-- crates/zed/src/languages/rust.rs | 14 +++-- crates/zed/src/languages/typescript.rs | 35 +++++++---- crates/zed/src/languages/yaml.rs | 24 +++++--- 14 files changed, 204 insertions(+), 146 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index c62bbbb146d2734e334d19dfcfd304e8a4b1d5e5..24fe2cb7bab9516073853f3c8d6f9463b354e34d 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -83,12 +83,17 @@ pub enum ServerExecutionKind { Node, } +#[derive(Debug, Clone, Deserialize)] +pub struct LanguageServerBinary { + pub path: PathBuf, + pub arguments: Vec, +} + /// Represents a Language Server, with certain cached sync properties. /// Uses [`LspAdapter`] under the hood, but calls all 'static' methods /// once at startup, and caches the results. pub struct CachedLspAdapter { pub name: LanguageServerName, - pub server_args: Vec, pub initialization_options: Option, pub disk_based_diagnostic_sources: Vec, pub disk_based_diagnostics_progress_token: Option, @@ -99,7 +104,6 @@ pub struct CachedLspAdapter { impl CachedLspAdapter { pub async fn new(adapter: Arc) -> Arc { let name = adapter.name().await; - let server_args = adapter.server_args().await; let initialization_options = adapter.initialization_options().await; let disk_based_diagnostic_sources = adapter.disk_based_diagnostic_sources().await; let disk_based_diagnostics_progress_token = @@ -108,7 +112,6 @@ impl CachedLspAdapter { Arc::new(CachedLspAdapter { name, - server_args, initialization_options, disk_based_diagnostic_sources, disk_based_diagnostics_progress_token, @@ -129,13 +132,16 @@ impl CachedLspAdapter { version: Box, http: Arc, container_dir: PathBuf, - ) -> Result { + ) -> Result { self.adapter .fetch_server_binary(version, http, container_dir) .await } - pub async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { + pub async fn cached_server_binary( + &self, + container_dir: PathBuf, + ) -> Option { self.adapter.cached_server_binary(container_dir).await } @@ -190,9 +196,9 @@ pub trait LspAdapter: 'static + Send + Sync { version: Box, http: Arc, container_dir: PathBuf, - ) -> Result; + ) -> Result; - async fn cached_server_binary(&self, container_dir: PathBuf) -> Option; + async fn cached_server_binary(&self, container_dir: PathBuf) -> Option; async fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {} @@ -215,10 +221,6 @@ pub trait LspAdapter: 'static + Send + Sync { None } - async fn server_args(&self) -> Vec { - Vec::new() - } - async fn initialization_options(&self) -> Option { None } @@ -497,7 +499,7 @@ pub struct LanguageRegistry { lsp_binary_paths: Mutex< HashMap< LanguageServerName, - Shared>>>, + Shared>>>, >, >, executor: Option>, @@ -810,12 +812,12 @@ impl LanguageRegistry { login_shell_env_loaded.await; let node_path = node_path.await; - let server_binary_path = this + let server_binary = this .lsp_binary_paths .lock() .entry(adapter.name.clone()) .or_insert_with(|| { - get_server_binary_path( + get_server_binary( adapter.clone(), language.clone(), http_client, @@ -829,8 +831,9 @@ impl LanguageRegistry { .clone() .map_err(|e| anyhow!(e)); - let server_binary_path = server_binary_path.await?; - let server_name = server_binary_path + let server_binary = server_binary.await?; + let server_name = server_binary + .path .file_name() .map(|name| name.to_string_lossy().to_string()); @@ -839,16 +842,15 @@ impl LanguageRegistry { let node_path = node_path .ok_or(anyhow!("Missing Node path for Node based language server"))?; let node_binary = node_path.join("bin/node"); - dbg!(&node_binary); let mut command = smol::process::Command::new(node_binary); - command.arg(dbg!(server_binary_path)); + command.arg(dbg!(server_binary.path)); command } - ServerExecutionKind::Launch => smol::process::Command::new(server_binary_path), + ServerExecutionKind::Launch => smol::process::Command::new(server_binary.path), }; - command.args(&adapter.server_args); + command.args(dbg!(&server_binary.arguments)); let server = lsp::LanguageServer::new(server_id, server_name, command, &root_path, cx)?; Ok(server) @@ -880,13 +882,13 @@ impl Default for LanguageRegistry { } } -async fn get_server_binary_path( +async fn get_server_binary( adapter: Arc, language: Arc, http_client: Arc, download_dir: Arc, statuses: async_broadcast::Sender<(Arc, LanguageServerBinaryStatus)>, -) -> Result { +) -> Result { let container_dir = download_dir.join(adapter.name.0.as_ref()); if !container_dir.exists() { smol::fs::create_dir_all(&container_dir) @@ -894,7 +896,7 @@ async fn get_server_binary_path( .context("failed to create container directory")?; } - let path = fetch_latest_server_binary_path( + let binary = fetch_latest_server_binary( adapter.clone(), language.clone(), http_client, @@ -902,12 +904,12 @@ async fn get_server_binary_path( statuses.clone(), ) .await; - if let Err(error) = path.as_ref() { - if let Some(cached_path) = adapter.cached_server_binary(container_dir).await { + if let Err(error) = binary.as_ref() { + if let Some(cached) = adapter.cached_server_binary(container_dir).await { statuses .broadcast((language.clone(), LanguageServerBinaryStatus::Cached)) .await?; - return Ok(cached_path); + return Ok(cached); } else { statuses .broadcast(( @@ -919,16 +921,16 @@ async fn get_server_binary_path( .await?; } } - path + binary } -async fn fetch_latest_server_binary_path( +async fn fetch_latest_server_binary( adapter: Arc, language: Arc, http_client: Arc, container_dir: &Path, lsp_binary_statuses_tx: async_broadcast::Sender<(Arc, LanguageServerBinaryStatus)>, -) -> Result { +) -> Result { let container_dir: Arc = container_dir.into(); lsp_binary_statuses_tx .broadcast(( @@ -942,13 +944,13 @@ async fn fetch_latest_server_binary_path( lsp_binary_statuses_tx .broadcast((language.clone(), LanguageServerBinaryStatus::Downloading)) .await?; - let path = adapter + let binary = adapter .fetch_server_binary(version_info, http_client, container_dir.to_path_buf()) .await?; lsp_binary_statuses_tx .broadcast((language.clone(), LanguageServerBinaryStatus::Downloaded)) .await?; - Ok(path) + Ok(binary) } impl Language { @@ -1485,11 +1487,11 @@ impl LspAdapter for Arc { _: Box, _: Arc, _: PathBuf, - ) -> Result { + ) -> Result { unreachable!(); } - async fn cached_server_binary(&self, _: PathBuf) -> Option { + async fn cached_server_binary(&self, _: PathBuf) -> Option { unreachable!(); } diff --git a/crates/zed/src/languages/c.rs b/crates/zed/src/languages/c.rs index 826de55874248556a84641a8985591ddb5aa7cd7..afd94604d9c148a575b8107b553cc4e2b4b910f5 100644 --- a/crates/zed/src/languages/c.rs +++ b/crates/zed/src/languages/c.rs @@ -43,7 +43,7 @@ impl super::LspAdapter for CLspAdapter { version: Box, http: Arc, container_dir: PathBuf, - ) -> Result { + ) -> Result { let version = version.downcast::().unwrap(); let zip_path = container_dir.join(format!("clangd_{}.zip", version.name)); let version_dir = container_dir.join(format!("clangd_{}", version.name)); @@ -85,10 +85,13 @@ impl super::LspAdapter for CLspAdapter { } } - Ok(binary_path) + Ok(LanguageServerBinary { + path: binary_path, + arguments: vec![], + }) } - async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { + async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { (|| async move { let mut last_clangd_dir = None; let mut entries = fs::read_dir(&container_dir).await?; @@ -101,7 +104,10 @@ impl super::LspAdapter for CLspAdapter { let clangd_dir = last_clangd_dir.ok_or_else(|| anyhow!("no cached binary"))?; let clangd_bin = clangd_dir.join("bin/clangd"); if clangd_bin.exists() { - Ok(clangd_bin) + Ok(LanguageServerBinary { + path: clangd_bin, + arguments: vec![], + }) } else { Err(anyhow!( "missing clangd binary in directory {:?}", diff --git a/crates/zed/src/languages/elixir.rs b/crates/zed/src/languages/elixir.rs index 0bdbaddd681604257bf4f6b24378070a6ff69d68..390a35fa3d0fc880fc56561f4db4e5e37ffe2b33 100644 --- a/crates/zed/src/languages/elixir.rs +++ b/crates/zed/src/languages/elixir.rs @@ -44,7 +44,7 @@ impl LspAdapter for ElixirLspAdapter { version: Box, http: Arc, container_dir: PathBuf, - ) -> Result { + ) -> Result { let version = version.downcast::().unwrap(); let zip_path = container_dir.join(format!("elixir-ls_{}.zip", version.name)); let version_dir = container_dir.join(format!("elixir-ls_{}", version.name)); @@ -98,17 +98,24 @@ impl LspAdapter for ElixirLspAdapter { } } - Ok(binary_path) + Ok(LanguageServerBinary { + path: binary_path, + arguments: vec![], + }) } - async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { + async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { (|| async move { let mut last = None; let mut entries = fs::read_dir(&container_dir).await?; while let Some(entry) = entries.next().await { last = Some(entry?.path()); } - last.ok_or_else(|| anyhow!("no cached binary")) + last.map(|path| LanguageServerBinary { + path, + arguments: vec![], + }) + .ok_or_else(|| anyhow!("no cached binary")) })() .await .log_err() diff --git a/crates/zed/src/languages/go.rs b/crates/zed/src/languages/go.rs index 4e34b126a1d7fb41c45258bb987f2e8705144b2e..f51c57ab8c0ce4ff7148f8055db2eb6ee3bb35aa 100644 --- a/crates/zed/src/languages/go.rs +++ b/crates/zed/src/languages/go.rs @@ -10,6 +10,10 @@ use smol::{fs, process}; use std::{any::Any, ops::Range, path::PathBuf, str, sync::Arc}; use util::ResultExt; +fn server_binary_arguments() -> Vec { + vec!["-mode=stdio".into()] +} + #[derive(Copy, Clone)] pub struct GoLspAdapter; @@ -27,10 +31,6 @@ impl super::LspAdapter for GoLspAdapter { ServerExecutionKind::Launch } - async fn server_args(&self) -> Vec { - vec!["-mode=stdio".into()] - } - async fn fetch_latest_server_version( &self, http: Arc, @@ -51,7 +51,7 @@ impl super::LspAdapter for GoLspAdapter { version: Box, _: Arc, container_dir: PathBuf, - ) -> Result { + ) -> Result { let version = version.downcast::>().unwrap(); let this = *self; @@ -72,7 +72,10 @@ impl super::LspAdapter for GoLspAdapter { } } - return Ok(binary_path.to_path_buf()); + return Ok(LanguageServerBinary { + path: binary_path.to_path_buf(), + arguments: server_binary_arguments(), + }); } } } else if let Some(path) = this.cached_server_binary(container_dir.clone()).await { @@ -106,10 +109,13 @@ impl super::LspAdapter for GoLspAdapter { let binary_path = container_dir.join(&format!("gopls_{version}")); fs::rename(&installed_binary_path, &binary_path).await?; - Ok(binary_path.to_path_buf()) + Ok(LanguageServerBinary { + path: binary_path.to_path_buf(), + arguments: server_binary_arguments(), + }) } - async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { + async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { (|| async move { let mut last_binary_path = None; let mut entries = fs::read_dir(&container_dir).await?; @@ -126,7 +132,10 @@ impl super::LspAdapter for GoLspAdapter { } if let Some(path) = last_binary_path { - Ok(path) + Ok(LanguageServerBinary { + path, + arguments: server_binary_arguments(), + }) } else { Err(anyhow!("no cached binary")) } diff --git a/crates/zed/src/languages/html.rs b/crates/zed/src/languages/html.rs index f7ade20dfbaf9eb2d4c0662f67216c5cb692634d..8c0f8e026d81c36426e82f0ac1acbacf0418b34f 100644 --- a/crates/zed/src/languages/html.rs +++ b/crates/zed/src/languages/html.rs @@ -3,12 +3,16 @@ use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use client::http::HttpClient; use futures::StreamExt; -use language::{LanguageServerName, LspAdapter, ServerExecutionKind}; +use language::{LanguageServerBinary, LanguageServerName, LspAdapter, ServerExecutionKind}; use serde_json::json; use smol::fs; use std::{any::Any, path::PathBuf, sync::Arc}; use util::ResultExt; +fn server_binary_arguments() -> Vec { + vec!["--stdio".into()] +} + pub struct HtmlLspAdapter; impl HtmlLspAdapter { @@ -26,10 +30,6 @@ impl LspAdapter for HtmlLspAdapter { ServerExecutionKind::Node } - async fn server_args(&self) -> Vec { - vec!["--stdio".into()] - } - async fn fetch_latest_server_version( &self, http: Arc, @@ -45,7 +45,7 @@ impl LspAdapter for HtmlLspAdapter { version: Box, http: Arc, container_dir: PathBuf, - ) -> Result { + ) -> Result { let version = version.downcast::().unwrap(); let version_dir = container_dir.join(version.as_str()); fs::create_dir_all(&version_dir) @@ -73,10 +73,13 @@ impl LspAdapter for HtmlLspAdapter { } } - Ok(binary_path) + Ok(LanguageServerBinary { + path: binary_path, + arguments: server_binary_arguments(), + }) } - async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { + async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { (|| async move { let mut last_version_dir = None; let mut entries = fs::read_dir(&container_dir).await?; @@ -89,7 +92,10 @@ impl LspAdapter for HtmlLspAdapter { let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?; let bin_path = last_version_dir.join(Self::BIN_PATH); if bin_path.exists() { - Ok(bin_path) + Ok(LanguageServerBinary { + path: bin_path, + arguments: server_binary_arguments(), + }) } else { Err(anyhow!( "missing executable in directory {:?}", diff --git a/crates/zed/src/languages/installation.rs b/crates/zed/src/languages/installation.rs index 852b8f0de0b9f47083e7dfbc97939c4abc231922..0eb6e0b7603101444f090d6103f67142bb1f2b81 100644 --- a/crates/zed/src/languages/installation.rs +++ b/crates/zed/src/languages/installation.rs @@ -48,8 +48,8 @@ pub async fn ensure_node_installation_dir(http: Arc) -> Result

) -> Result

) -> Result

, name: &str) -> Result { diff --git a/crates/zed/src/languages/json.rs b/crates/zed/src/languages/json.rs index 9df5bec6c0572e9c4151561b5d64b6e123eb16f3..7c07165b2bcd21a744c4f3d5a36663472277bad8 100644 --- a/crates/zed/src/languages/json.rs +++ b/crates/zed/src/languages/json.rs @@ -6,7 +6,9 @@ use client::http::HttpClient; use collections::HashMap; use futures::{future::BoxFuture, io::BufReader, FutureExt, StreamExt}; use gpui::MutableAppContext; -use language::{LanguageRegistry, LanguageServerName, LspAdapter, ServerExecutionKind}; +use language::{ + LanguageRegistry, LanguageServerBinary, LanguageServerName, LspAdapter, ServerExecutionKind, +}; use serde_json::json; use settings::{keymap_file_json_schema, settings_file_json_schema}; use smol::fs::{self, File}; @@ -20,6 +22,10 @@ use std::{ use theme::ThemeRegistry; use util::{paths, ResultExt, StaffMode}; +fn server_binary_arguments() -> Vec { + vec!["--stdio".into()] +} + pub struct JsonLspAdapter { languages: Arc, themes: Arc, @@ -41,10 +47,6 @@ impl LspAdapter for JsonLspAdapter { ServerExecutionKind::Node } - async fn server_args(&self) -> Vec { - vec!["--stdio".into()] - } - async fn fetch_latest_server_version( &self, http: Arc, @@ -68,7 +70,7 @@ impl LspAdapter for JsonLspAdapter { version: Box, http: Arc, container_dir: PathBuf, - ) -> Result { + ) -> Result { let version = version.downcast::().unwrap(); let destination_path = container_dir.join(format!( "json-language-server-{}-{}", @@ -102,17 +104,23 @@ impl LspAdapter for JsonLspAdapter { } } - Ok(destination_path) + Ok(LanguageServerBinary { + path: destination_path, + arguments: server_binary_arguments(), + }) } - async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { + async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { (|| async move { let mut last = None; let mut entries = fs::read_dir(&container_dir).await?; while let Some(entry) = entries.next().await { last = Some(entry?.path()); } - last.ok_or_else(|| anyhow!("no cached binary")) + anyhow::Ok(LanguageServerBinary { + path: last.ok_or_else(|| anyhow!("no cached binary"))?, + arguments: server_binary_arguments(), + }) })() .await .log_err() diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index de3cabcef8f75160fe4b77e18ade83921c6a493e..9f12878990561d38e1e7e42221bf015ffbf94e92 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -4,7 +4,7 @@ use client::http::HttpClient; use collections::HashMap; use futures::lock::Mutex; use gpui::executor::Background; -use language::{LanguageServerName, LspAdapter, ServerExecutionKind}; +use language::{LanguageServerBinary, LanguageServerName, LspAdapter, ServerExecutionKind}; use plugin_runtime::{Plugin, PluginBinary, PluginBuilder, WasiFn}; use std::{any::Any, path::PathBuf, sync::Arc}; use util::ResultExt; @@ -33,10 +33,9 @@ pub async fn new_json(executor: Arc) -> Result { pub struct PluginLspAdapter { name: WasiFn<(), String>, server_execution_kind: WasiFn<(), ServerExecutionKind>, - server_args: WasiFn<(), Vec>, fetch_latest_server_version: WasiFn<(), Option>, - fetch_server_binary: WasiFn<(PathBuf, String), Result>, - cached_server_binary: WasiFn>, + fetch_server_binary: WasiFn<(PathBuf, String), Result>, + cached_server_binary: WasiFn>, initialization_options: WasiFn<(), String>, language_ids: WasiFn<(), Vec<(String, String)>>, executor: Arc, @@ -49,7 +48,6 @@ impl PluginLspAdapter { Ok(Self { name: plugin.function("name")?, server_execution_kind: plugin.function("server_execution_kind")?, - server_args: plugin.function("server_args")?, fetch_latest_server_version: plugin.function("fetch_latest_server_version")?, fetch_server_binary: plugin.function("fetch_server_binary")?, cached_server_binary: plugin.function("cached_server_binary")?, @@ -83,15 +81,6 @@ impl LspAdapter for PluginLspAdapter { .unwrap() } - async fn server_args<'a>(&'a self) -> Vec { - self.runtime - .lock() - .await - .call(&self.server_args, ()) - .await - .unwrap() - } - async fn fetch_latest_server_version( &self, _: Arc, @@ -116,7 +105,7 @@ impl LspAdapter for PluginLspAdapter { version: Box, _: Arc, container_dir: PathBuf, - ) -> Result { + ) -> Result { let version = *version.downcast::().unwrap(); let runtime = self.runtime.clone(); let function = self.fetch_server_binary; @@ -124,7 +113,7 @@ impl LspAdapter for PluginLspAdapter { .spawn(async move { let mut runtime = runtime.lock().await; let handle = runtime.attach_path(&container_dir)?; - let result: Result = + let result: Result = runtime.call(&function, (container_dir, version)).await?; runtime.remove_resource(handle)?; result.map_err(|e| anyhow!("{}", e)) @@ -132,7 +121,7 @@ impl LspAdapter for PluginLspAdapter { .await } - async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { + async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { let runtime = self.runtime.clone(); let function = self.cached_server_binary; @@ -140,7 +129,8 @@ impl LspAdapter for PluginLspAdapter { .spawn(async move { let mut runtime = runtime.lock().await; let handle = runtime.attach_path(&container_dir).ok()?; - let result: Option = runtime.call(&function, container_dir).await.ok()?; + let result: Option = + runtime.call(&function, container_dir).await.ok()?; runtime.remove_resource(handle).ok()?; result }) diff --git a/crates/zed/src/languages/lua.rs b/crates/zed/src/languages/lua.rs index c51235e1f172a3c99c216f47ccf1125b4261e832..2ee67016ddf7f2337560ccb5db1f454d492a0d4b 100644 --- a/crates/zed/src/languages/lua.rs +++ b/crates/zed/src/languages/lua.rs @@ -6,7 +6,7 @@ use async_tar::Archive; use async_trait::async_trait; use client::http::HttpClient; use futures::{io::BufReader, StreamExt}; -use language::{LanguageServerName, ServerExecutionKind}; +use language::{LanguageServerBinary, LanguageServerName, ServerExecutionKind}; use smol::fs; use util::{async_iife, ResultExt}; @@ -15,6 +15,13 @@ use super::installation::{latest_github_release, GitHubLspBinaryVersion}; #[derive(Copy, Clone)] pub struct LuaLspAdapter; +fn server_binary_arguments() -> Vec { + vec![ + "--logpath=~/lua-language-server.log".into(), + "--loglevel=trace".into(), + ] +} + #[async_trait] impl super::LspAdapter for LuaLspAdapter { async fn name(&self) -> LanguageServerName { @@ -25,13 +32,6 @@ impl super::LspAdapter for LuaLspAdapter { ServerExecutionKind::Launch } - async fn server_args(&self) -> Vec { - vec![ - "--logpath=~/lua-language-server.log".into(), - "--loglevel=trace".into(), - ] - } - async fn fetch_latest_server_version( &self, http: Arc, @@ -61,7 +61,7 @@ impl super::LspAdapter for LuaLspAdapter { version: Box, http: Arc, container_dir: PathBuf, - ) -> Result { + ) -> Result { let version = version.downcast::().unwrap(); let binary_path = container_dir.join("bin/lua-language-server"); @@ -81,10 +81,13 @@ impl super::LspAdapter for LuaLspAdapter { ::from_mode(0o755), ) .await?; - Ok(binary_path) + Ok(LanguageServerBinary { + path: binary_path, + arguments: server_binary_arguments(), + }) } - async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { + async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { async_iife!({ let mut last_binary_path = None; let mut entries = fs::read_dir(&container_dir).await?; @@ -101,7 +104,10 @@ impl super::LspAdapter for LuaLspAdapter { } if let Some(path) = last_binary_path { - Ok(path) + Ok(LanguageServerBinary { + path, + arguments: server_binary_arguments(), + }) } else { Err(anyhow!("no cached binary")) } diff --git a/crates/zed/src/languages/python.rs b/crates/zed/src/languages/python.rs index 4495b9e4d8e9ae15e4c16e3326d51ae9a6110f9c..ece8f7036212dc0f1a4e3c7b5984761808c78a57 100644 --- a/crates/zed/src/languages/python.rs +++ b/crates/zed/src/languages/python.rs @@ -3,13 +3,17 @@ use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use client::http::HttpClient; use futures::StreamExt; -use language::{LanguageServerName, LspAdapter, ServerExecutionKind}; +use language::{LanguageServerBinary, LanguageServerName, LspAdapter, ServerExecutionKind}; use smol::fs; use std::{any::Any, path::PathBuf, sync::Arc}; use util::ResultExt; pub struct PythonLspAdapter; +fn server_binary_arguments() -> Vec { + vec!["--stdio".into()] +} + impl PythonLspAdapter { const BIN_PATH: &'static str = "node_modules/pyright/langserver.index.js"; } @@ -24,10 +28,6 @@ impl LspAdapter for PythonLspAdapter { ServerExecutionKind::Node } - async fn server_args(&self) -> Vec { - vec!["--stdio".into()] - } - async fn fetch_latest_server_version( &self, http: Arc, @@ -40,7 +40,7 @@ impl LspAdapter for PythonLspAdapter { version: Box, http: Arc, container_dir: PathBuf, - ) -> Result { + ) -> Result { let version = version.downcast::().unwrap(); let version_dir = container_dir.join(version.as_str()); fs::create_dir_all(&version_dir) @@ -63,10 +63,13 @@ impl LspAdapter for PythonLspAdapter { } } - Ok(binary_path) + Ok(LanguageServerBinary { + path: binary_path, + arguments: server_binary_arguments(), + }) } - async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { + async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { (|| async move { let mut last_version_dir = None; let mut entries = fs::read_dir(&container_dir).await?; @@ -79,7 +82,10 @@ impl LspAdapter for PythonLspAdapter { let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?; let bin_path = last_version_dir.join(Self::BIN_PATH); if bin_path.exists() { - Ok(bin_path) + Ok(LanguageServerBinary { + path: bin_path, + arguments: server_binary_arguments(), + }) } else { Err(anyhow!( "missing executable in directory {:?}", diff --git a/crates/zed/src/languages/ruby.rs b/crates/zed/src/languages/ruby.rs index 1308610a5a9223b9070a786f904c95affe4c5f3b..f30a536746bd5c388e6c07fc60fa705bf3d3619f 100644 --- a/crates/zed/src/languages/ruby.rs +++ b/crates/zed/src/languages/ruby.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use client::http::HttpClient; -use language::{LanguageServerName, LspAdapter, ServerExecutionKind}; +use language::{LanguageServerBinary, LanguageServerName, LspAdapter, ServerExecutionKind}; use std::{any::Any, path::PathBuf, sync::Arc}; pub struct RubyLanguageServer; @@ -16,10 +16,6 @@ impl LspAdapter for RubyLanguageServer { ServerExecutionKind::Launch } - async fn server_args(&self) -> Vec { - vec!["stdio".into()] - } - async fn fetch_latest_server_version( &self, _: Arc, @@ -32,12 +28,15 @@ impl LspAdapter for RubyLanguageServer { _version: Box, _: Arc, _container_dir: PathBuf, - ) -> Result { + ) -> Result { Err(anyhow!("solargraph must be installed manually")) } - async fn cached_server_binary(&self, _container_dir: PathBuf) -> Option { - Some("solargraph".into()) + async fn cached_server_binary(&self, _container_dir: PathBuf) -> Option { + Some(LanguageServerBinary { + path: "solargraph".into(), + arguments: vec!["stdio".into()], + }) } async fn label_for_completion( diff --git a/crates/zed/src/languages/rust.rs b/crates/zed/src/languages/rust.rs index 1f9940ac0d45bab21996590edbf4c76be7026e54..5eac0fca0a07f6aa2a6b9a204456ff3329c0dce7 100644 --- a/crates/zed/src/languages/rust.rs +++ b/crates/zed/src/languages/rust.rs @@ -46,7 +46,7 @@ impl LspAdapter for RustLspAdapter { version: Box, http: Arc, container_dir: PathBuf, - ) -> Result { + ) -> Result { let version = version.downcast::().unwrap(); let destination_path = container_dir.join(format!("rust-analyzer-{}", version.name)); @@ -76,17 +76,23 @@ impl LspAdapter for RustLspAdapter { } } - Ok(destination_path) + Ok(LanguageServerBinary { + path: destination_path, + arguments: Default::default(), + }) } - async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { + async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { (|| async move { let mut last = None; let mut entries = fs::read_dir(&container_dir).await?; while let Some(entry) = entries.next().await { last = Some(entry?.path()); } - last.ok_or_else(|| anyhow!("no cached binary")) + anyhow::Ok(LanguageServerBinary { + path: last.ok_or_else(|| anyhow!("no cached binary"))?, + arguments: Default::default(), + }) })() .await .log_err() diff --git a/crates/zed/src/languages/typescript.rs b/crates/zed/src/languages/typescript.rs index 7775da6067045bcd0feb77193da8e1809d36e3aa..316f5d7627690a01e704b5c8f721c4ab9dde9345 100644 --- a/crates/zed/src/languages/typescript.rs +++ b/crates/zed/src/languages/typescript.rs @@ -3,12 +3,19 @@ use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use client::http::HttpClient; use futures::StreamExt; -use language::{LanguageServerName, LspAdapter, ServerExecutionKind}; +use language::{LanguageServerBinary, LanguageServerName, LspAdapter, ServerExecutionKind}; use serde_json::json; use smol::fs; use std::{any::Any, path::PathBuf, sync::Arc}; use util::ResultExt; +fn server_binary_arguments() -> Vec { + ["--stdio", "--tsserver-path", "node_modules/typescript/lib"] + .into_iter() + .map(Into::into) + .collect() +} + pub struct TypeScriptLspAdapter; impl TypeScriptLspAdapter { @@ -31,13 +38,6 @@ impl LspAdapter for TypeScriptLspAdapter { ServerExecutionKind::Node } - async fn server_args(&self) -> Vec { - ["--stdio", "--tsserver-path", "node_modules/typescript/lib"] - .into_iter() - .map(str::to_string) - .collect() - } - async fn fetch_latest_server_version( &self, http: Arc, @@ -53,7 +53,7 @@ impl LspAdapter for TypeScriptLspAdapter { versions: Box, http: Arc, container_dir: PathBuf, - ) -> Result { + ) -> Result { let versions = versions.downcast::().unwrap(); let version_dir = container_dir.join(&format!( "typescript-{}:server-{}", @@ -90,10 +90,13 @@ impl LspAdapter for TypeScriptLspAdapter { } } - Ok(binary_path) + Ok(LanguageServerBinary { + path: binary_path, + arguments: server_binary_arguments(), + }) } - async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { + async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { (|| async move { let mut last_version_dir = None; let mut entries = fs::read_dir(&container_dir).await?; @@ -107,9 +110,15 @@ impl LspAdapter for TypeScriptLspAdapter { let old_bin_path = last_version_dir.join(Self::OLD_BIN_PATH); let new_bin_path = last_version_dir.join(Self::NEW_BIN_PATH); if new_bin_path.exists() { - Ok(new_bin_path) + Ok(LanguageServerBinary { + path: new_bin_path, + arguments: server_binary_arguments(), + }) } else if old_bin_path.exists() { - Ok(old_bin_path) + Ok(LanguageServerBinary { + path: old_bin_path, + arguments: server_binary_arguments(), + }) } else { Err(anyhow!( "missing executable in directory {:?}", diff --git a/crates/zed/src/languages/yaml.rs b/crates/zed/src/languages/yaml.rs index da03e6d5cfd215df5747203fc1f2564fb69468a4..a9b9fbe1efe4de81619e6a116f62d1b967bbffbd 100644 --- a/crates/zed/src/languages/yaml.rs +++ b/crates/zed/src/languages/yaml.rs @@ -3,7 +3,7 @@ use async_trait::async_trait; use client::http::HttpClient; use futures::{future::BoxFuture, FutureExt, StreamExt}; use gpui::MutableAppContext; -use language::{LanguageServerName, LspAdapter, ServerExecutionKind}; +use language::{LanguageServerBinary, LanguageServerName, LspAdapter, ServerExecutionKind}; use serde_json::Value; use settings::Settings; use smol::fs; @@ -12,6 +12,10 @@ use util::ResultExt; use super::installation::{npm_install_packages, npm_package_latest_version}; +fn server_binary_arguments() -> Vec { + vec!["--stdio".into()] +} + pub struct YamlLspAdapter; impl YamlLspAdapter { @@ -28,10 +32,6 @@ impl LspAdapter for YamlLspAdapter { ServerExecutionKind::Node } - async fn server_args(&self) -> Vec { - vec!["--stdio".into()] - } - async fn fetch_latest_server_version( &self, http: Arc, @@ -44,7 +44,7 @@ impl LspAdapter for YamlLspAdapter { version: Box, http: Arc, container_dir: PathBuf, - ) -> Result { + ) -> Result { let version = version.downcast::().unwrap(); let version_dir = container_dir.join(version.as_str()); fs::create_dir_all(&version_dir) @@ -72,10 +72,13 @@ impl LspAdapter for YamlLspAdapter { } } - Ok(binary_path) + Ok(LanguageServerBinary { + path: binary_path, + arguments: server_binary_arguments(), + }) } - async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { + async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { (|| async move { let mut last_version_dir = None; let mut entries = fs::read_dir(&container_dir).await?; @@ -88,7 +91,10 @@ impl LspAdapter for YamlLspAdapter { let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?; let bin_path = last_version_dir.join(Self::BIN_PATH); if bin_path.exists() { - Ok(bin_path) + Ok(LanguageServerBinary { + path: bin_path, + arguments: server_binary_arguments(), + }) } else { Err(anyhow!( "missing executable in directory {:?}", From c72d33e0294483a29d96a371f46232c98c99bfff Mon Sep 17 00:00:00 2001 From: Julia Date: Sun, 26 Mar 2023 23:59:49 -0400 Subject: [PATCH 5/9] Initial impl of `NodeRuntime` w/JSON borked and a deadlock :) Co-Authored-By: Antonio Scandurra --- crates/language/src/language.rs | 64 +++----- crates/lsp/src/lsp.rs | 17 ++- crates/zed/src/languages.rs | 36 +++-- crates/zed/src/languages/c.rs | 4 - crates/zed/src/languages/elixir.rs | 4 - crates/zed/src/languages/go.rs | 8 +- crates/zed/src/languages/html.rs | 70 +++++---- crates/zed/src/languages/installation.rs | 119 +-------------- crates/zed/src/languages/json.rs | 41 +++-- crates/zed/src/languages/language_plugin.rs | 13 +- crates/zed/src/languages/lua.rs | 10 +- crates/zed/src/languages/node_runtime.rs | 159 ++++++++++++++++++++ crates/zed/src/languages/python.rs | 55 ++++--- crates/zed/src/languages/ruby.rs | 6 +- crates/zed/src/languages/rust.rs | 4 - crates/zed/src/languages/typescript.rs | 100 ++++++------ crates/zed/src/languages/yaml.rs | 66 ++++---- crates/zed/src/main.rs | 18 +-- crates/zed/src/zed.rs | 8 +- plugins/json_language/src/lib.rs | 10 +- 20 files changed, 436 insertions(+), 376 deletions(-) create mode 100644 crates/zed/src/languages/node_runtime.rs diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 24fe2cb7bab9516073853f3c8d6f9463b354e34d..dc9a74e1445f3ff03cdf8cee9511f7c8d09acf86 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -29,6 +29,7 @@ use std::{ any::Any, borrow::Cow, cell::RefCell, + ffi::OsString, fmt::Debug, hash::Hash, mem, @@ -86,7 +87,7 @@ pub enum ServerExecutionKind { #[derive(Debug, Clone, Deserialize)] pub struct LanguageServerBinary { pub path: PathBuf, - pub arguments: Vec, + pub arguments: Vec, } /// Represents a Language Server, with certain cached sync properties. @@ -184,8 +185,6 @@ impl CachedLspAdapter { pub trait LspAdapter: 'static + Send + Sync { async fn name(&self) -> LanguageServerName; - async fn server_execution_kind(&self) -> ServerExecutionKind; - async fn fetch_latest_server_version( &self, http: Arc, @@ -494,7 +493,6 @@ pub struct LanguageRegistry { lsp_binary_statuses_tx: async_broadcast::Sender<(Arc, LanguageServerBinaryStatus)>, lsp_binary_statuses_rx: async_broadcast::Receiver<(Arc, LanguageServerBinaryStatus)>, login_shell_env_loaded: Shared>, - node_path: Shared>>, #[allow(clippy::type_complexity)] lsp_binary_paths: Mutex< HashMap< @@ -516,7 +514,7 @@ struct LanguageRegistryState { } impl LanguageRegistry { - pub fn new(login_shell_env_loaded: Task<()>, node_path: Task>) -> Self { + pub fn new(login_shell_env_loaded: Task<()>) -> Self { let (lsp_binary_statuses_tx, lsp_binary_statuses_rx) = async_broadcast::broadcast(16); Self { state: RwLock::new(LanguageRegistryState { @@ -532,7 +530,6 @@ impl LanguageRegistry { lsp_binary_statuses_tx, lsp_binary_statuses_rx, login_shell_env_loaded: login_shell_env_loaded.shared(), - node_path: node_path.shared(), lsp_binary_paths: Default::default(), executor: None, } @@ -540,7 +537,7 @@ impl LanguageRegistry { #[cfg(any(test, feature = "test-support"))] pub fn test() -> Self { - Self::new(Task::ready(()), Task::Ready(None)) + Self::new(Task::ready(())) } pub fn set_executor(&mut self, executor: Arc) { @@ -795,6 +792,8 @@ impl LanguageRegistry { Ok(server) })); } + + dbg!(); let download_dir = self .language_server_download_dir @@ -806,18 +805,18 @@ impl LanguageRegistry { let adapter = language.adapter.clone()?; let lsp_binary_statuses = self.lsp_binary_statuses_tx.clone(); let login_shell_env_loaded = self.login_shell_env_loaded.clone(); - let node_path = self.node_path.clone(); + dbg!(); Some(cx.spawn(|cx| async move { login_shell_env_loaded.await; - let node_path = node_path.await; - let server_binary = this + let binary = this .lsp_binary_paths .lock() .entry(adapter.name.clone()) .or_insert_with(|| { - get_server_binary( + dbg!(); + get_binary( adapter.clone(), language.clone(), http_client, @@ -829,30 +828,19 @@ impl LanguageRegistry { .shared() }) .clone() - .map_err(|e| anyhow!(e)); - - let server_binary = server_binary.await?; - let server_name = server_binary - .path - .file_name() - .map(|name| name.to_string_lossy().to_string()); - - let mut command = match adapter.adapter.server_execution_kind().await { - ServerExecutionKind::Node => { - let node_path = node_path - .ok_or(anyhow!("Missing Node path for Node based language server"))?; - let node_binary = node_path.join("bin/node"); - let mut command = smol::process::Command::new(node_binary); - command.arg(dbg!(server_binary.path)); - command - } - - ServerExecutionKind::Launch => smol::process::Command::new(server_binary.path), - }; + .map_err(|e| anyhow!(e)) + .await?; + dbg!(); - command.args(dbg!(&server_binary.arguments)); - let server = lsp::LanguageServer::new(server_id, server_name, command, &root_path, cx)?; + let server = lsp::LanguageServer::new( + server_id, + &binary.path, + &binary.arguments, + &root_path, + cx, + )?; + dbg!(); Ok(server) })) } @@ -882,7 +870,7 @@ impl Default for LanguageRegistry { } } -async fn get_server_binary( +async fn get_binary( adapter: Arc, language: Arc, http_client: Arc, @@ -896,7 +884,7 @@ async fn get_server_binary( .context("failed to create container directory")?; } - let binary = fetch_latest_server_binary( + let binary = fetch_latest_binary( adapter.clone(), language.clone(), http_client, @@ -924,7 +912,7 @@ async fn get_server_binary( binary } -async fn fetch_latest_server_binary( +async fn fetch_latest_binary( adapter: Arc, language: Arc, http_client: Arc, @@ -1471,10 +1459,6 @@ impl LspAdapter for Arc { LanguageServerName(self.name.into()) } - async fn server_execution_kind(&self) -> ServerExecutionKind { - ServerExecutionKind::Launch - } - async fn fetch_latest_server_version( &self, _: Arc, diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 87dbf95b11835e9c432b5ac4fd471330f9738b29..80bf0a70f65fdf8b541da65fdb0f41f1bf8c6dbf 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -105,10 +105,10 @@ struct Error { } impl LanguageServer { - pub fn new( + pub fn new>( server_id: usize, - server_name: Option, - mut command: process::Command, + binary_path: &Path, + arguments: &[T], root_path: &Path, cx: AsyncAppContext, ) -> Result { @@ -118,13 +118,14 @@ impl LanguageServer { root_path.parent().unwrap_or_else(|| Path::new("/")) }; - let mut server = dbg!(command + let mut server = process::Command::new(binary_path) .current_dir(working_dir) + .args(arguments) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::inherit()) - .kill_on_drop(true)) - .spawn()?; + .kill_on_drop(true) + .spawn()?; let stdin = server.stdin.take().unwrap(); let stout = server.stdout.take().unwrap(); @@ -147,8 +148,8 @@ impl LanguageServer { }, ); - if let Some(name) = server_name { - server.name = name; + if let Some(name) = binary_path.file_name() { + server.name = name.to_string_lossy().to_string(); } Ok(server) } diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 6bf1d94c77051f38b4fc5414b833bd3ba7f1b66c..8eae806fdb9e825afb0a2cc6b3ddcd14573186c9 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -1,5 +1,8 @@ use anyhow::Context; +use client::http::HttpClient; +use gpui::executor::Background; pub use language::*; +use node_runtime::NodeRuntime; use rust_embed::RustEmbed; use std::{borrow::Cow, str, sync::Arc}; use theme::ThemeRegistry; @@ -12,14 +15,13 @@ mod installation; mod json; mod language_plugin; mod lua; +mod node_runtime; mod python; mod ruby; mod rust; mod typescript; mod yaml; -pub use installation::ensure_node_installation_dir; - // 1. Add tree-sitter-{language} parser to zed crate // 2. Create a language directory in zed/crates/zed/src/languages and add the language to init function below // 3. Add config.toml to the newly created language directory using existing languages as a template @@ -34,7 +36,14 @@ pub use installation::ensure_node_installation_dir; #[exclude = "*.rs"] struct LanguageDir; -pub fn init(languages: Arc, themes: Arc) { +pub fn init( + http: Arc, + background: Arc, + languages: Arc, + themes: Arc, +) { + let node_runtime = NodeRuntime::new(http, background); + for (name, grammar, lsp_adapter) in [ ( "c", @@ -65,6 +74,7 @@ pub fn init(languages: Arc, themes: Arc) { "json", tree_sitter_json::language(), Some(Arc::new(json::JsonLspAdapter::new( + node_runtime.clone(), languages.clone(), themes.clone(), ))), @@ -77,7 +87,9 @@ pub fn init(languages: Arc, themes: Arc) { ( "python", tree_sitter_python::language(), - Some(Arc::new(python::PythonLspAdapter)), + Some(Arc::new(python::PythonLspAdapter::new( + node_runtime.clone(), + ))), ), ( "rust", @@ -92,22 +104,28 @@ pub fn init(languages: Arc, themes: Arc) { ( "tsx", tree_sitter_typescript::language_tsx(), - Some(Arc::new(typescript::TypeScriptLspAdapter)), + Some(Arc::new(typescript::TypeScriptLspAdapter::new( + node_runtime.clone(), + ))), ), ( "typescript", tree_sitter_typescript::language_typescript(), - Some(Arc::new(typescript::TypeScriptLspAdapter)), + Some(Arc::new(typescript::TypeScriptLspAdapter::new( + node_runtime.clone(), + ))), ), ( "javascript", tree_sitter_typescript::language_tsx(), - Some(Arc::new(typescript::TypeScriptLspAdapter)), + Some(Arc::new(typescript::TypeScriptLspAdapter::new( + node_runtime.clone(), + ))), ), ( "html", tree_sitter_html::language(), - Some(Arc::new(html::HtmlLspAdapter)), + Some(Arc::new(html::HtmlLspAdapter::new(node_runtime.clone()))), ), ( "ruby", @@ -137,7 +155,7 @@ pub fn init(languages: Arc, themes: Arc) { ( "yaml", tree_sitter_yaml::language(), - Some(Arc::new(yaml::YamlLspAdapter)), + Some(Arc::new(yaml::YamlLspAdapter::new(node_runtime.clone()))), ), ] { languages.register(name, load_config(name), grammar, lsp_adapter, load_queries); diff --git a/crates/zed/src/languages/c.rs b/crates/zed/src/languages/c.rs index afd94604d9c148a575b8107b553cc4e2b4b910f5..1799dc2973a3fdb5312625ab1571f10c1bbfbf25 100644 --- a/crates/zed/src/languages/c.rs +++ b/crates/zed/src/languages/c.rs @@ -16,10 +16,6 @@ impl super::LspAdapter for CLspAdapter { LanguageServerName("clangd".into()) } - async fn server_execution_kind(&self) -> ServerExecutionKind { - ServerExecutionKind::Launch - } - async fn fetch_latest_server_version( &self, http: Arc, diff --git a/crates/zed/src/languages/elixir.rs b/crates/zed/src/languages/elixir.rs index 390a35fa3d0fc880fc56561f4db4e5e37ffe2b33..75b6ff18b2e1515f7c0bb2f1d9fe6d4314f879ee 100644 --- a/crates/zed/src/languages/elixir.rs +++ b/crates/zed/src/languages/elixir.rs @@ -17,10 +17,6 @@ impl LspAdapter for ElixirLspAdapter { LanguageServerName("elixir-ls".into()) } - async fn server_execution_kind(&self) -> ServerExecutionKind { - ServerExecutionKind::Launch - } - async fn fetch_latest_server_version( &self, http: Arc, diff --git a/crates/zed/src/languages/go.rs b/crates/zed/src/languages/go.rs index f51c57ab8c0ce4ff7148f8055db2eb6ee3bb35aa..b887aa10174bcb60cdf73db92cc87c6bda9758cf 100644 --- a/crates/zed/src/languages/go.rs +++ b/crates/zed/src/languages/go.rs @@ -7,10 +7,10 @@ pub use language::*; use lazy_static::lazy_static; use regex::Regex; use smol::{fs, process}; -use std::{any::Any, ops::Range, path::PathBuf, str, sync::Arc}; +use std::{any::Any, ffi::OsString, ops::Range, path::PathBuf, str, sync::Arc}; use util::ResultExt; -fn server_binary_arguments() -> Vec { +fn server_binary_arguments() -> Vec { vec!["-mode=stdio".into()] } @@ -27,10 +27,6 @@ impl super::LspAdapter for GoLspAdapter { LanguageServerName("gopls".into()) } - async fn server_execution_kind(&self) -> ServerExecutionKind { - ServerExecutionKind::Launch - } - async fn fetch_latest_server_version( &self, http: Arc, diff --git a/crates/zed/src/languages/html.rs b/crates/zed/src/languages/html.rs index 8c0f8e026d81c36426e82f0ac1acbacf0418b34f..a2cfbac96b4ff47b7cf06e49551185f1fa1892cd 100644 --- a/crates/zed/src/languages/html.rs +++ b/crates/zed/src/languages/html.rs @@ -1,23 +1,34 @@ -use super::installation::{npm_install_packages, npm_package_latest_version}; +use super::node_runtime::NodeRuntime; use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use client::http::HttpClient; use futures::StreamExt; -use language::{LanguageServerBinary, LanguageServerName, LspAdapter, ServerExecutionKind}; +use language::{LanguageServerBinary, LanguageServerName, LspAdapter}; use serde_json::json; use smol::fs; -use std::{any::Any, path::PathBuf, sync::Arc}; +use std::{ + any::Any, + ffi::OsString, + path::{Path, PathBuf}, + sync::Arc, +}; use util::ResultExt; -fn server_binary_arguments() -> Vec { - vec!["--stdio".into()] +fn server_binary_arguments(server_path: &Path) -> Vec { + vec![server_path.into(), "--stdio".into()] } -pub struct HtmlLspAdapter; +pub struct HtmlLspAdapter { + node: Arc, +} impl HtmlLspAdapter { - const BIN_PATH: &'static str = + const SERVER_PATH: &'static str = "node_modules/vscode-langservers-extracted/bin/vscode-html-language-server"; + + pub fn new(node: Arc) -> Self { + HtmlLspAdapter { node } + } } #[async_trait] @@ -26,24 +37,21 @@ impl LspAdapter for HtmlLspAdapter { LanguageServerName("vscode-html-language-server".into()) } - async fn server_execution_kind(&self) -> ServerExecutionKind { - ServerExecutionKind::Node - } - async fn fetch_latest_server_version( &self, - http: Arc, + _: Arc, ) -> Result> { - Ok( - Box::new(npm_package_latest_version(http, "vscode-langservers-extracted").await?) - as Box<_>, - ) + Ok(Box::new( + self.node + .npm_package_latest_version("vscode-langservers-extracted") + .await?, + ) as Box<_>) } async fn fetch_server_binary( &self, version: Box, - http: Arc, + _: Arc, container_dir: PathBuf, ) -> Result { let version = version.downcast::().unwrap(); @@ -51,15 +59,15 @@ impl LspAdapter for HtmlLspAdapter { fs::create_dir_all(&version_dir) .await .context("failed to create version directory")?; - let binary_path = version_dir.join(Self::BIN_PATH); + let server_path = version_dir.join(Self::SERVER_PATH); - if fs::metadata(&binary_path).await.is_err() { - npm_install_packages( - http, - [("vscode-langservers-extracted", version.as_str())], - &version_dir, - ) - .await?; + if fs::metadata(&server_path).await.is_err() { + self.node + .npm_install_packages( + [("vscode-langservers-extracted", version.as_str())], + &version_dir, + ) + .await?; if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() { while let Some(entry) = entries.next().await { @@ -74,8 +82,8 @@ impl LspAdapter for HtmlLspAdapter { } Ok(LanguageServerBinary { - path: binary_path, - arguments: server_binary_arguments(), + path: self.node.binary_path().await?, + arguments: server_binary_arguments(&server_path), }) } @@ -90,11 +98,11 @@ impl LspAdapter for HtmlLspAdapter { } } let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?; - let bin_path = last_version_dir.join(Self::BIN_PATH); - if bin_path.exists() { + let server_path = last_version_dir.join(Self::SERVER_PATH); + if server_path.exists() { Ok(LanguageServerBinary { - path: bin_path, - arguments: server_binary_arguments(), + path: self.node.binary_path().await?, + arguments: server_binary_arguments(&server_path), }) } else { Err(anyhow!( diff --git a/crates/zed/src/languages/installation.rs b/crates/zed/src/languages/installation.rs index 0eb6e0b7603101444f090d6103f67142bb1f2b81..8fdef507908bbd1690a2f0e7b1ce735827e2a04b 100644 --- a/crates/zed/src/languages/installation.rs +++ b/crates/zed/src/languages/installation.rs @@ -1,34 +1,14 @@ -use anyhow::{anyhow, Context, Result}; -use async_compression::futures::bufread::GzipDecoder; -use async_tar::Archive; +use anyhow::{Context, Result}; use client::http::HttpClient; -use futures::io::BufReader; use serde::Deserialize; -use smol::fs::{self}; use smol::io::AsyncReadExt; -use std::{ - path::{Path, PathBuf}, - sync::Arc, -}; +use std::sync::Arc; pub struct GitHubLspBinaryVersion { pub name: String, pub url: String, } -#[derive(Deserialize)] -#[serde(rename_all = "kebab-case")] -struct NpmInfo { - #[serde(default)] - dist_tags: NpmInfoDistTags, - versions: Vec, -} - -#[derive(Deserialize, Default)] -struct NpmInfoDistTags { - latest: Option, -} - #[derive(Deserialize)] pub(crate) struct GithubRelease { pub name: String, @@ -41,101 +21,6 @@ pub(crate) struct GithubReleaseAsset { pub browser_download_url: String, } -pub async fn ensure_node_installation_dir(http: Arc) -> Result { - eprintln!("ensure_node_installation_dir"); - - let version = "v18.15.0"; - let arch = "arm64"; - - let folder_name = format!("node-{version}-darwin-{arch}"); - let node_containing_dir = util::paths::SUPPORT_DIR.join("node"); - let node_dir = node_containing_dir.join(folder_name); - let node_binary = node_dir.join("bin/node"); - - if fs::metadata(&node_binary).await.is_err() { - _ = fs::remove_dir_all(&node_containing_dir).await; - fs::create_dir(&node_containing_dir) - .await - .context("error creating node containing dir")?; - - let url = format!("https://nodejs.org/dist/{version}/node-{version}-darwin-{arch}.tar.gz"); - let mut response = http - .get(&url, Default::default(), true) - .await - .context("error downloading Node binary tarball")?; - - let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut())); - let archive = Archive::new(decompressed_bytes); - archive.unpack(&node_containing_dir).await?; - eprintln!("unpacked"); - } - - Ok(node_dir) -} - -pub async fn npm_package_latest_version(http: Arc, name: &str) -> Result { - let node_dir = ensure_node_installation_dir(http).await?; - let node_binary = node_dir.join("bin/npm"); - let npm_file = node_dir.join("bin/npm"); - - let output = smol::process::Command::new(node_binary) - .arg(npm_file) - .args(["-fetch-retry-mintimeout", "2000"]) - .args(["-fetch-retry-maxtimeout", "5000"]) - .args(["-fetch-timeout", "5000"]) - .args(["info", name, "--json"]) - .output() - .await - .context("failed to run npm info")?; - if !output.status.success() { - Err(anyhow!( - "failed to execute npm info:\nstdout: {:?}\nstderr: {:?}", - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - ))?; - } - let mut info: NpmInfo = serde_json::from_slice(&output.stdout)?; - info.dist_tags - .latest - .or_else(|| info.versions.pop()) - .ok_or_else(|| anyhow!("no version found for npm package {}", name)) -} - -pub async fn npm_install_packages( - http: Arc, - packages: impl IntoIterator, - directory: &Path, -) -> Result<()> { - let node_dir = ensure_node_installation_dir(http).await?; - let node_binary = node_dir.join("bin/npm"); - let npm_file = node_dir.join("bin/npm"); - - let output = smol::process::Command::new(node_binary) - .arg(npm_file) - .args(["-fetch-retry-mintimeout", "2000"]) - .args(["-fetch-retry-maxtimeout", "5000"]) - .args(["-fetch-timeout", "5000"]) - .arg("install") - .arg("--prefix") - .arg(directory) - .args( - packages - .into_iter() - .map(|(name, version)| format!("{name}@{version}")), - ) - .output() - .await - .context("failed to run npm install")?; - if !output.status.success() { - Err(anyhow!( - "failed to execute npm install:\nstdout: {:?}\nstderr: {:?}", - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - ))?; - } - Ok(()) -} - pub(crate) async fn latest_github_release( repo_name_with_owner: &str, http: Arc, diff --git a/crates/zed/src/languages/json.rs b/crates/zed/src/languages/json.rs index 7c07165b2bcd21a744c4f3d5a36663472277bad8..10665d96b61f67c9ed4e1b177f331795e7a10fce 100644 --- a/crates/zed/src/languages/json.rs +++ b/crates/zed/src/languages/json.rs @@ -1,4 +1,7 @@ -use super::installation::{latest_github_release, GitHubLspBinaryVersion}; +use super::{ + installation::{latest_github_release, GitHubLspBinaryVersion}, + node_runtime::NodeRuntime, +}; use anyhow::{anyhow, Result}; use async_compression::futures::bufread::GzipDecoder; use async_trait::async_trait; @@ -6,15 +9,14 @@ use client::http::HttpClient; use collections::HashMap; use futures::{future::BoxFuture, io::BufReader, FutureExt, StreamExt}; use gpui::MutableAppContext; -use language::{ - LanguageRegistry, LanguageServerBinary, LanguageServerName, LspAdapter, ServerExecutionKind, -}; +use language::{LanguageRegistry, LanguageServerBinary, LanguageServerName, LspAdapter}; use serde_json::json; use settings::{keymap_file_json_schema, settings_file_json_schema}; use smol::fs::{self, File}; use std::{ any::Any, env::consts, + ffi::OsString, future, path::{Path, PathBuf}, sync::Arc, @@ -22,18 +24,27 @@ use std::{ use theme::ThemeRegistry; use util::{paths, ResultExt, StaffMode}; -fn server_binary_arguments() -> Vec { - vec!["--stdio".into()] +fn server_binary_arguments(server_path: &Path) -> Vec { + dbg!(vec![server_path.into(), "--stdio".into()]) } pub struct JsonLspAdapter { + node: Arc, languages: Arc, themes: Arc, } impl JsonLspAdapter { - pub fn new(languages: Arc, themes: Arc) -> Self { - Self { languages, themes } + pub fn new( + node: Arc, + languages: Arc, + themes: Arc, + ) -> Self { + JsonLspAdapter { + node, + languages, + themes, + } } } @@ -43,10 +54,6 @@ impl LspAdapter for JsonLspAdapter { LanguageServerName("json-language-server".into()) } - async fn server_execution_kind(&self) -> ServerExecutionKind { - ServerExecutionKind::Node - } - async fn fetch_latest_server_version( &self, http: Arc, @@ -105,8 +112,8 @@ impl LspAdapter for JsonLspAdapter { } Ok(LanguageServerBinary { - path: destination_path, - arguments: server_binary_arguments(), + path: self.node.binary_path().await?, + arguments: server_binary_arguments(&destination_path), }) } @@ -118,8 +125,10 @@ impl LspAdapter for JsonLspAdapter { last = Some(entry?.path()); } anyhow::Ok(LanguageServerBinary { - path: last.ok_or_else(|| anyhow!("no cached binary"))?, - arguments: server_binary_arguments(), + path: self.node.binary_path().await?, + arguments: server_binary_arguments( + &last.ok_or_else(|| anyhow!("no cached binary"))?, + ), }) })() .await diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index 9f12878990561d38e1e7e42221bf015ffbf94e92..38f50d2d88710d66d43cdb7cafe932f92c1be57d 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -4,7 +4,7 @@ use client::http::HttpClient; use collections::HashMap; use futures::lock::Mutex; use gpui::executor::Background; -use language::{LanguageServerBinary, LanguageServerName, LspAdapter, ServerExecutionKind}; +use language::{LanguageServerBinary, LanguageServerName, LspAdapter}; use plugin_runtime::{Plugin, PluginBinary, PluginBuilder, WasiFn}; use std::{any::Any, path::PathBuf, sync::Arc}; use util::ResultExt; @@ -32,7 +32,6 @@ pub async fn new_json(executor: Arc) -> Result { pub struct PluginLspAdapter { name: WasiFn<(), String>, - server_execution_kind: WasiFn<(), ServerExecutionKind>, fetch_latest_server_version: WasiFn<(), Option>, fetch_server_binary: WasiFn<(PathBuf, String), Result>, cached_server_binary: WasiFn>, @@ -47,7 +46,6 @@ impl PluginLspAdapter { pub async fn new(mut plugin: Plugin, executor: Arc) -> Result { Ok(Self { name: plugin.function("name")?, - server_execution_kind: plugin.function("server_execution_kind")?, fetch_latest_server_version: plugin.function("fetch_latest_server_version")?, fetch_server_binary: plugin.function("fetch_server_binary")?, cached_server_binary: plugin.function("cached_server_binary")?, @@ -72,15 +70,6 @@ impl LspAdapter for PluginLspAdapter { LanguageServerName(name.into()) } - async fn server_execution_kind(&self) -> ServerExecutionKind { - self.runtime - .lock() - .await - .call(&self.server_execution_kind, ()) - .await - .unwrap() - } - async fn fetch_latest_server_version( &self, _: Arc, diff --git a/crates/zed/src/languages/lua.rs b/crates/zed/src/languages/lua.rs index 2ee67016ddf7f2337560ccb5db1f454d492a0d4b..37c3e36489022d8a2ff51ffaa9843ffbd8dcbc08 100644 --- a/crates/zed/src/languages/lua.rs +++ b/crates/zed/src/languages/lua.rs @@ -1,4 +1,4 @@ -use std::{any::Any, env::consts, path::PathBuf, sync::Arc}; +use std::{any::Any, env::consts, ffi::OsString, path::PathBuf, sync::Arc}; use anyhow::{anyhow, bail, Result}; use async_compression::futures::bufread::GzipDecoder; @@ -6,7 +6,7 @@ use async_tar::Archive; use async_trait::async_trait; use client::http::HttpClient; use futures::{io::BufReader, StreamExt}; -use language::{LanguageServerBinary, LanguageServerName, ServerExecutionKind}; +use language::{LanguageServerBinary, LanguageServerName}; use smol::fs; use util::{async_iife, ResultExt}; @@ -15,7 +15,7 @@ use super::installation::{latest_github_release, GitHubLspBinaryVersion}; #[derive(Copy, Clone)] pub struct LuaLspAdapter; -fn server_binary_arguments() -> Vec { +fn server_binary_arguments() -> Vec { vec![ "--logpath=~/lua-language-server.log".into(), "--loglevel=trace".into(), @@ -28,10 +28,6 @@ impl super::LspAdapter for LuaLspAdapter { LanguageServerName("lua-language-server".into()) } - async fn server_execution_kind(&self) -> ServerExecutionKind { - ServerExecutionKind::Launch - } - async fn fetch_latest_server_version( &self, http: Arc, diff --git a/crates/zed/src/languages/node_runtime.rs b/crates/zed/src/languages/node_runtime.rs new file mode 100644 index 0000000000000000000000000000000000000000..da4ce42b43d15b84f0f5bc6e26ecf4696a5f81d5 --- /dev/null +++ b/crates/zed/src/languages/node_runtime.rs @@ -0,0 +1,159 @@ +use anyhow::{anyhow, Context, Result}; +use async_compression::futures::bufread::GzipDecoder; +use async_tar::Archive; +use client::http::HttpClient; +use futures::{future::Shared, FutureExt, TryFutureExt}; +use gpui::{executor::Background, Task}; +use parking_lot::Mutex; +use serde::Deserialize; +use smol::{fs, io::BufReader}; +use std::{ + path::{Path, PathBuf}, + sync::Arc, +}; + +#[derive(Deserialize)] +#[serde(rename_all = "kebab-case")] +pub struct NpmInfo { + #[serde(default)] + dist_tags: NpmInfoDistTags, + versions: Vec, +} + +#[derive(Deserialize, Default)] +pub struct NpmInfoDistTags { + latest: Option, +} + +pub struct NodeRuntime { + http: Arc, + background: Arc, + installation_path: Mutex>>>>>, +} + +impl NodeRuntime { + pub fn new(http: Arc, background: Arc) -> Arc { + Arc::new(NodeRuntime { + http, + background, + installation_path: Mutex::new(None), + }) + } + + pub async fn binary_path(&self) -> Result { + let installation_path = self.install_if_needed().await?; + Ok(installation_path.join("bin/node")) + } + + pub async fn npm_package_latest_version(&self, name: &str) -> Result { + let installation_path = self.install_if_needed().await?; + let node_binary = installation_path.join("bin/node"); + let npm_file = installation_path.join("bin/npm"); + + let output = smol::process::Command::new(node_binary) + .arg(npm_file) + .args(["-fetch-retry-mintimeout", "2000"]) + .args(["-fetch-retry-maxtimeout", "5000"]) + .args(["-fetch-timeout", "5000"]) + .args(["info", name, "--json"]) + .output() + .await + .context("failed to run npm info")?; + if !output.status.success() { + Err(anyhow!( + "failed to execute npm info:\nstdout: {:?}\nstderr: {:?}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ))?; + } + let mut info: NpmInfo = serde_json::from_slice(&output.stdout)?; + info.dist_tags + .latest + .or_else(|| info.versions.pop()) + .ok_or_else(|| anyhow!("no version found for npm package {}", name)) + } + + pub async fn npm_install_packages( + &self, + packages: impl IntoIterator, + directory: &Path, + ) -> Result<()> { + let installation_path = self.install_if_needed().await?; + let node_binary = installation_path.join("bin/node"); + let npm_file = installation_path.join("bin/npm"); + + let output = smol::process::Command::new(node_binary) + .arg(npm_file) + .args(["-fetch-retry-mintimeout", "2000"]) + .args(["-fetch-retry-maxtimeout", "5000"]) + .args(["-fetch-timeout", "5000"]) + .arg("install") + .arg("--prefix") + .arg(directory) + .args( + packages + .into_iter() + .map(|(name, version)| format!("{name}@{version}")), + ) + .output() + .await + .context("failed to run npm install")?; + if !output.status.success() { + Err(anyhow!( + "failed to execute npm install:\nstdout: {:?}\nstderr: {:?}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ))?; + } + Ok(()) + } + + async fn install_if_needed(&self) -> Result { + let task = self + .installation_path + .lock() + .get_or_insert_with(|| { + let http = self.http.clone(); + self.background + .spawn( + async move { + let version = "v18.15.0"; + let arch = "arm64"; + + let folder_name = format!("node-{version}-darwin-{arch}"); + let node_containing_dir = util::paths::SUPPORT_DIR.join("node"); + let node_dir = node_containing_dir.join(folder_name); + let node_binary = node_dir.join("bin/node"); + + if fs::metadata(&node_binary).await.is_err() { + _ = fs::remove_dir_all(&node_containing_dir).await; + fs::create_dir(&node_containing_dir) + .await + .context("error creating node containing dir")?; + + let url = format!("https://nodejs.org/dist/{version}/node-{version}-darwin-{arch}.tar.gz"); + let mut response = + http.get(&url, Default::default(), true) + .await + .context("error downloading Node binary tarball")?; + + let decompressed_bytes = + GzipDecoder::new(BufReader::new(response.body_mut())); + let archive = Archive::new(decompressed_bytes); + archive.unpack(&node_containing_dir).await?; + } + + anyhow::Ok(node_dir) + } + .map_err(Arc::new), + ) + .shared() + }) + .clone(); + + match task.await { + Ok(path) => Ok(path), + Err(error) => Err(anyhow!("{}", error)), + } + } +} diff --git a/crates/zed/src/languages/python.rs b/crates/zed/src/languages/python.rs index ece8f7036212dc0f1a4e3c7b5984761808c78a57..9a09c63bb6ca2447667ba41e484a0fb990412522 100644 --- a/crates/zed/src/languages/python.rs +++ b/crates/zed/src/languages/python.rs @@ -1,21 +1,32 @@ -use super::installation::{npm_install_packages, npm_package_latest_version}; +use super::node_runtime::NodeRuntime; use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use client::http::HttpClient; use futures::StreamExt; -use language::{LanguageServerBinary, LanguageServerName, LspAdapter, ServerExecutionKind}; +use language::{LanguageServerBinary, LanguageServerName, LspAdapter}; use smol::fs; -use std::{any::Any, path::PathBuf, sync::Arc}; +use std::{ + any::Any, + ffi::OsString, + path::{Path, PathBuf}, + sync::Arc, +}; use util::ResultExt; -pub struct PythonLspAdapter; +fn server_binary_arguments(server_path: &Path) -> Vec { + vec![server_path.into(), "--stdio".into()] +} -fn server_binary_arguments() -> Vec { - vec!["--stdio".into()] +pub struct PythonLspAdapter { + node: Arc, } impl PythonLspAdapter { - const BIN_PATH: &'static str = "node_modules/pyright/langserver.index.js"; + const SERVER_PATH: &'static str = "node_modules/pyright/langserver.index.js"; + + pub fn new(node: Arc) -> Self { + PythonLspAdapter { node } + } } #[async_trait] @@ -24,21 +35,17 @@ impl LspAdapter for PythonLspAdapter { LanguageServerName("pyright".into()) } - async fn server_execution_kind(&self) -> ServerExecutionKind { - ServerExecutionKind::Node - } - async fn fetch_latest_server_version( &self, - http: Arc, + _: Arc, ) -> Result> { - Ok(Box::new(npm_package_latest_version(http, "pyright").await?) as Box<_>) + Ok(Box::new(self.node.npm_package_latest_version("pyright").await?) as Box<_>) } async fn fetch_server_binary( &self, version: Box, - http: Arc, + _: Arc, container_dir: PathBuf, ) -> Result { let version = version.downcast::().unwrap(); @@ -46,10 +53,12 @@ impl LspAdapter for PythonLspAdapter { fs::create_dir_all(&version_dir) .await .context("failed to create version directory")?; - let binary_path = version_dir.join(Self::BIN_PATH); + let server_path = version_dir.join(Self::SERVER_PATH); - if fs::metadata(&binary_path).await.is_err() { - npm_install_packages(http, [("pyright", version.as_str())], &version_dir).await?; + if fs::metadata(&server_path).await.is_err() { + self.node + .npm_install_packages([("pyright", version.as_str())], &version_dir) + .await?; if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() { while let Some(entry) = entries.next().await { @@ -64,8 +73,8 @@ impl LspAdapter for PythonLspAdapter { } Ok(LanguageServerBinary { - path: binary_path, - arguments: server_binary_arguments(), + path: self.node.binary_path().await?, + arguments: server_binary_arguments(&server_path), }) } @@ -80,11 +89,11 @@ impl LspAdapter for PythonLspAdapter { } } let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?; - let bin_path = last_version_dir.join(Self::BIN_PATH); - if bin_path.exists() { + let server_path = last_version_dir.join(Self::SERVER_PATH); + if server_path.exists() { Ok(LanguageServerBinary { - path: bin_path, - arguments: server_binary_arguments(), + path: self.node.binary_path().await?, + arguments: server_binary_arguments(&server_path), }) } else { Err(anyhow!( diff --git a/crates/zed/src/languages/ruby.rs b/crates/zed/src/languages/ruby.rs index f30a536746bd5c388e6c07fc60fa705bf3d3619f..662c1f464d1fd1f216b0af37925950658e083ba4 100644 --- a/crates/zed/src/languages/ruby.rs +++ b/crates/zed/src/languages/ruby.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use client::http::HttpClient; -use language::{LanguageServerBinary, LanguageServerName, LspAdapter, ServerExecutionKind}; +use language::{LanguageServerBinary, LanguageServerName, LspAdapter}; use std::{any::Any, path::PathBuf, sync::Arc}; pub struct RubyLanguageServer; @@ -12,10 +12,6 @@ impl LspAdapter for RubyLanguageServer { LanguageServerName("solargraph".into()) } - async fn server_execution_kind(&self) -> ServerExecutionKind { - ServerExecutionKind::Launch - } - async fn fetch_latest_server_version( &self, _: Arc, diff --git a/crates/zed/src/languages/rust.rs b/crates/zed/src/languages/rust.rs index 5eac0fca0a07f6aa2a6b9a204456ff3329c0dce7..2fe063ed5ee8b1ee6bdca66361e245ec34d61324 100644 --- a/crates/zed/src/languages/rust.rs +++ b/crates/zed/src/languages/rust.rs @@ -19,10 +19,6 @@ impl LspAdapter for RustLspAdapter { LanguageServerName("rust-analyzer".into()) } - async fn server_execution_kind(&self) -> ServerExecutionKind { - ServerExecutionKind::Launch - } - async fn fetch_latest_server_version( &self, http: Arc, diff --git a/crates/zed/src/languages/typescript.rs b/crates/zed/src/languages/typescript.rs index 316f5d7627690a01e704b5c8f721c4ab9dde9345..f9baf4f8f78f14c9ab6a7d7616b3affd3b992c37 100644 --- a/crates/zed/src/languages/typescript.rs +++ b/crates/zed/src/languages/typescript.rs @@ -1,26 +1,39 @@ -use super::installation::{npm_install_packages, npm_package_latest_version}; +use super::node_runtime::NodeRuntime; use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use client::http::HttpClient; use futures::StreamExt; -use language::{LanguageServerBinary, LanguageServerName, LspAdapter, ServerExecutionKind}; +use language::{LanguageServerBinary, LanguageServerName, LspAdapter}; use serde_json::json; use smol::fs; -use std::{any::Any, path::PathBuf, sync::Arc}; +use std::{ + any::Any, + ffi::OsString, + path::{Path, PathBuf}, + sync::Arc, +}; use util::ResultExt; -fn server_binary_arguments() -> Vec { - ["--stdio", "--tsserver-path", "node_modules/typescript/lib"] - .into_iter() - .map(Into::into) - .collect() +fn server_binary_arguments(server_path: &Path) -> Vec { + vec![ + server_path.into(), + "--stdio".into(), + "--tsserver-path".into(), + "node_modules/typescript/lib".into(), + ] } -pub struct TypeScriptLspAdapter; +pub struct TypeScriptLspAdapter { + node: Arc, +} impl TypeScriptLspAdapter { - const OLD_BIN_PATH: &'static str = "node_modules/typescript-language-server/lib/cli.js"; - const NEW_BIN_PATH: &'static str = "node_modules/typescript-language-server/lib/cli.mjs"; + const OLD_SERVER_PATH: &'static str = "node_modules/typescript-language-server/lib/cli.js"; + const NEW_SERVER_PATH: &'static str = "node_modules/typescript-language-server/lib/cli.mjs"; + + pub fn new(node: Arc) -> Self { + TypeScriptLspAdapter { node } + } } struct Versions { @@ -34,24 +47,23 @@ impl LspAdapter for TypeScriptLspAdapter { LanguageServerName("typescript-language-server".into()) } - async fn server_execution_kind(&self) -> ServerExecutionKind { - ServerExecutionKind::Node - } - async fn fetch_latest_server_version( &self, - http: Arc, + _: Arc, ) -> Result> { Ok(Box::new(Versions { - typescript_version: npm_package_latest_version(http.clone(), "typescript").await?, - server_version: npm_package_latest_version(http, "typescript-language-server").await?, + typescript_version: self.node.npm_package_latest_version("typescript").await?, + server_version: self + .node + .npm_package_latest_version("typescript-language-server") + .await?, }) as Box<_>) } async fn fetch_server_binary( &self, versions: Box, - http: Arc, + _: Arc, container_dir: PathBuf, ) -> Result { let versions = versions.downcast::().unwrap(); @@ -62,21 +74,21 @@ impl LspAdapter for TypeScriptLspAdapter { fs::create_dir_all(&version_dir) .await .context("failed to create version directory")?; - let binary_path = version_dir.join(Self::NEW_BIN_PATH); - - if fs::metadata(&binary_path).await.is_err() { - npm_install_packages( - http, - [ - ("typescript", versions.typescript_version.as_str()), - ( - "typescript-language-server", - versions.server_version.as_str(), - ), - ], - &version_dir, - ) - .await?; + let server_path = version_dir.join(Self::NEW_SERVER_PATH); + + if fs::metadata(&server_path).await.is_err() { + self.node + .npm_install_packages( + [ + ("typescript", versions.typescript_version.as_str()), + ( + "typescript-language-server", + versions.server_version.as_str(), + ), + ], + &version_dir, + ) + .await?; if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() { while let Some(entry) = entries.next().await { @@ -91,8 +103,8 @@ impl LspAdapter for TypeScriptLspAdapter { } Ok(LanguageServerBinary { - path: binary_path, - arguments: server_binary_arguments(), + path: self.node.binary_path().await?, + arguments: server_binary_arguments(&server_path), }) } @@ -107,17 +119,17 @@ impl LspAdapter for TypeScriptLspAdapter { } } let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?; - let old_bin_path = last_version_dir.join(Self::OLD_BIN_PATH); - let new_bin_path = last_version_dir.join(Self::NEW_BIN_PATH); - if new_bin_path.exists() { + let old_server_path = last_version_dir.join(Self::OLD_SERVER_PATH); + let new_server_path = last_version_dir.join(Self::NEW_SERVER_PATH); + if new_server_path.exists() { Ok(LanguageServerBinary { - path: new_bin_path, - arguments: server_binary_arguments(), + path: self.node.binary_path().await?, + arguments: server_binary_arguments(&new_server_path), }) - } else if old_bin_path.exists() { + } else if old_server_path.exists() { Ok(LanguageServerBinary { - path: old_bin_path, - arguments: server_binary_arguments(), + path: self.node.binary_path().await?, + arguments: server_binary_arguments(&old_server_path), }) } else { Err(anyhow!( diff --git a/crates/zed/src/languages/yaml.rs b/crates/zed/src/languages/yaml.rs index a9b9fbe1efe4de81619e6a116f62d1b967bbffbd..b6e82842dea80c777c1885e33fedfaf8fda439d5 100644 --- a/crates/zed/src/languages/yaml.rs +++ b/crates/zed/src/languages/yaml.rs @@ -1,25 +1,36 @@ +use super::node_runtime::NodeRuntime; use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use client::http::HttpClient; use futures::{future::BoxFuture, FutureExt, StreamExt}; use gpui::MutableAppContext; -use language::{LanguageServerBinary, LanguageServerName, LspAdapter, ServerExecutionKind}; +use language::{LanguageServerBinary, LanguageServerName, LspAdapter}; use serde_json::Value; use settings::Settings; use smol::fs; -use std::{any::Any, future, path::PathBuf, sync::Arc}; +use std::{ + any::Any, + ffi::OsString, + future, + path::{Path, PathBuf}, + sync::Arc, +}; use util::ResultExt; -use super::installation::{npm_install_packages, npm_package_latest_version}; - -fn server_binary_arguments() -> Vec { - vec!["--stdio".into()] +fn server_binary_arguments(server_path: &Path) -> Vec { + vec![server_path.into(), "--stdio".into()] } -pub struct YamlLspAdapter; +pub struct YamlLspAdapter { + node: Arc, +} impl YamlLspAdapter { - const BIN_PATH: &'static str = "node_modules/yaml-language-server/bin/yaml-language-server"; + const SERVER_PATH: &'static str = "node_modules/yaml-language-server/bin/yaml-language-server"; + + pub fn new(node: Arc) -> Self { + YamlLspAdapter { node } + } } #[async_trait] @@ -28,21 +39,21 @@ impl LspAdapter for YamlLspAdapter { LanguageServerName("yaml-language-server".into()) } - async fn server_execution_kind(&self) -> ServerExecutionKind { - ServerExecutionKind::Node - } - async fn fetch_latest_server_version( &self, - http: Arc, + _: Arc, ) -> Result> { - Ok(Box::new(npm_package_latest_version(http, "yaml-language-server").await?) as Box<_>) + Ok(Box::new( + self.node + .npm_package_latest_version("yaml-language-server") + .await?, + ) as Box<_>) } async fn fetch_server_binary( &self, version: Box, - http: Arc, + _: Arc, container_dir: PathBuf, ) -> Result { let version = version.downcast::().unwrap(); @@ -50,15 +61,12 @@ impl LspAdapter for YamlLspAdapter { fs::create_dir_all(&version_dir) .await .context("failed to create version directory")?; - let binary_path = version_dir.join(Self::BIN_PATH); + let server_path = version_dir.join(Self::SERVER_PATH); - if fs::metadata(&binary_path).await.is_err() { - npm_install_packages( - http, - [("yaml-language-server", version.as_str())], - &version_dir, - ) - .await?; + if fs::metadata(&server_path).await.is_err() { + self.node + .npm_install_packages([("yaml-language-server", version.as_str())], &version_dir) + .await?; if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() { while let Some(entry) = entries.next().await { @@ -73,8 +81,8 @@ impl LspAdapter for YamlLspAdapter { } Ok(LanguageServerBinary { - path: binary_path, - arguments: server_binary_arguments(), + path: self.node.binary_path().await?, + arguments: server_binary_arguments(&server_path), }) } @@ -89,11 +97,11 @@ impl LspAdapter for YamlLspAdapter { } } let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?; - let bin_path = last_version_dir.join(Self::BIN_PATH); - if bin_path.exists() { + let server_path = last_version_dir.join(Self::SERVER_PATH); + if server_path.exists() { Ok(LanguageServerBinary { - path: bin_path, - arguments: server_binary_arguments(), + path: self.node.binary_path().await?, + arguments: server_binary_arguments(&server_path), }) } else { Err(anyhow!( diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index d794e4c39d307365be25ebaf42c7d7f690a3b085..fb6c6227c35af0bf350580e873f591629532c781 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -81,15 +81,6 @@ fn main() { }) }; - let node_path = { - let http = http.clone(); - app.background().spawn(async move { - languages::ensure_node_installation_dir(http.clone()) - .await - .log_err() - }) - }; - let (cli_connections_tx, mut cli_connections_rx) = mpsc::unbounded(); let (open_paths_tx, mut open_paths_rx) = mpsc::unbounded(); app.on_open_urls(move |urls, _| { @@ -144,11 +135,16 @@ fn main() { } let client = client::Client::new(http.clone(), cx); - let mut languages = LanguageRegistry::new(login_shell_env_loaded, node_path); + let mut languages = LanguageRegistry::new(login_shell_env_loaded); languages.set_executor(cx.background().clone()); languages.set_language_server_download_dir(paths::LANGUAGES_DIR.clone()); let languages = Arc::new(languages); - languages::init(languages.clone(), themes.clone()); + languages::init( + http.clone(), + cx.background().clone(), + languages.clone(), + themes.clone(), + ); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx)); cx.set_global(client.clone()); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index fd6b56027279be1b4579551c4cfbbe6375afa3fb..788be77e7587b46f191d344867a96ca9af7539f8 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -652,6 +652,7 @@ fn open_bundled_file( mod tests { use super::*; use assets::Assets; + use client::test::FakeHttpClient; use editor::{scroll::autoscroll::Autoscroll, DisplayPoint, Editor}; use gpui::{ executor::Deterministic, AssetSource, MutableAppContext, TestAppContext, ViewHandle, @@ -1850,7 +1851,12 @@ mod tests { languages.set_executor(cx.background().clone()); let languages = Arc::new(languages); let themes = ThemeRegistry::new((), cx.font_cache().clone()); - languages::init(languages.clone(), themes); + languages::init( + FakeHttpClient::with_404_response(), + cx.background().clone(), + languages.clone(), + themes, + ); for name in languages.language_names() { languages.language_for_name(&name); } diff --git a/plugins/json_language/src/lib.rs b/plugins/json_language/src/lib.rs index b3c70da7a1d4b097be6c7704bb7bc1dd18c05f38..e18d9ce74b288fe8209d74ddf8d9552808817900 100644 --- a/plugins/json_language/src/lib.rs +++ b/plugins/json_language/src/lib.rs @@ -6,7 +6,7 @@ use serde::Deserialize; #[import] fn command(string: &str) -> Option>; -const BIN_PATH: &str = "node_modules/vscode-json-languageserver/bin/vscode-json-languageserver"; +const SERVER_PATH: &str = "node_modules/vscode-json-languageserver/bin/vscode-json-languageserver"; #[export] pub fn name() -> &'static str { @@ -38,7 +38,7 @@ pub fn fetch_server_binary(container_dir: PathBuf, version: String) -> Result Option { } let last_version_dir = last_version_dir?; - let bin_path = last_version_dir.join(BIN_PATH); - if bin_path.exists() { - Some(bin_path) + let server_path = last_version_dir.join(SERVER_PATH); + if server_path.exists() { + Some(server_path) } else { println!("no binary found"); None From df4380b06638d75fda44cfe6cb2db38bfd33cde2 Mon Sep 17 00:00:00 2001 From: Julia Date: Mon, 27 Mar 2023 11:05:17 -0400 Subject: [PATCH 6/9] Download aarch64 or x64 Node binary according to system architecture --- crates/language/src/language.rs | 2 +- crates/zed/src/languages/node_runtime.rs | 73 +++++++++++++----------- 2 files changed, 40 insertions(+), 35 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index dc9a74e1445f3ff03cdf8cee9511f7c8d09acf86..a4bf37c78b2ae2d51904c01e7ece7289e82f8220 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -792,7 +792,7 @@ impl LanguageRegistry { Ok(server) })); } - + dbg!(); let download_dir = self diff --git a/crates/zed/src/languages/node_runtime.rs b/crates/zed/src/languages/node_runtime.rs index da4ce42b43d15b84f0f5bc6e26ecf4696a5f81d5..7f3b138f2a40936e7736a542083e093f275cc710 100644 --- a/crates/zed/src/languages/node_runtime.rs +++ b/crates/zed/src/languages/node_runtime.rs @@ -1,17 +1,20 @@ -use anyhow::{anyhow, Context, Result}; +use anyhow::{anyhow, bail, Context, Result}; use async_compression::futures::bufread::GzipDecoder; use async_tar::Archive; use client::http::HttpClient; -use futures::{future::Shared, FutureExt, TryFutureExt}; +use futures::{future::Shared, FutureExt}; use gpui::{executor::Background, Task}; use parking_lot::Mutex; use serde::Deserialize; use smol::{fs, io::BufReader}; use std::{ + env::consts, path::{Path, PathBuf}, sync::Arc, }; +const VERSION: &str = "v18.15.0"; + #[derive(Deserialize)] #[serde(rename_all = "kebab-case")] pub struct NpmInfo { @@ -115,38 +118,7 @@ impl NodeRuntime { .get_or_insert_with(|| { let http = self.http.clone(); self.background - .spawn( - async move { - let version = "v18.15.0"; - let arch = "arm64"; - - let folder_name = format!("node-{version}-darwin-{arch}"); - let node_containing_dir = util::paths::SUPPORT_DIR.join("node"); - let node_dir = node_containing_dir.join(folder_name); - let node_binary = node_dir.join("bin/node"); - - if fs::metadata(&node_binary).await.is_err() { - _ = fs::remove_dir_all(&node_containing_dir).await; - fs::create_dir(&node_containing_dir) - .await - .context("error creating node containing dir")?; - - let url = format!("https://nodejs.org/dist/{version}/node-{version}-darwin-{arch}.tar.gz"); - let mut response = - http.get(&url, Default::default(), true) - .await - .context("error downloading Node binary tarball")?; - - let decompressed_bytes = - GzipDecoder::new(BufReader::new(response.body_mut())); - let archive = Archive::new(decompressed_bytes); - archive.unpack(&node_containing_dir).await?; - } - - anyhow::Ok(node_dir) - } - .map_err(Arc::new), - ) + .spawn(async move { Self::install(http).await.map_err(Arc::new) }) .shared() }) .clone(); @@ -156,4 +128,37 @@ impl NodeRuntime { Err(error) => Err(anyhow!("{}", error)), } } + + async fn install(http: Arc) -> Result { + let arch = match consts::ARCH { + "x86_64" => "x64", + "aarch64" => "arm64", + other => bail!("Running on unsupported platform: {other}"), + }; + + let folder_name = format!("node-{VERSION}-darwin-{arch}"); + let node_containing_dir = util::paths::SUPPORT_DIR.join("node"); + let node_dir = node_containing_dir.join(folder_name); + let node_binary = node_dir.join("bin/node"); + + if fs::metadata(&node_binary).await.is_err() { + _ = fs::remove_dir_all(&node_containing_dir).await; + fs::create_dir(&node_containing_dir) + .await + .context("error creating node containing dir")?; + + let file_name = format!("node-{VERSION}-darwin-{arch}.tar.gz"); + let url = format!("https://nodejs.org/dist/{VERSION}/{file_name}"); + let mut response = http + .get(&url, Default::default(), true) + .await + .context("error downloading Node binary tarball")?; + + let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut())); + let archive = Archive::new(decompressed_bytes); + archive.unpack(&node_containing_dir).await?; + } + + anyhow::Ok(node_dir) + } } From d4560fe32143018681d65aed428b0dbd6ae3d78e Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 28 Mar 2023 10:18:22 -0400 Subject: [PATCH 7/9] Prevent deadlock when multiple languages attempt to install Node at once --- crates/language/src/language.rs | 18 ++++++------------ crates/zed/src/languages/node_runtime.rs | 2 ++ 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index a4bf37c78b2ae2d51904c01e7ece7289e82f8220..69053e9fc4c58e0711efa9e1ecb3f9c9833d242a 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -793,8 +793,6 @@ impl LanguageRegistry { })); } - dbg!(); - let download_dir = self .language_server_download_dir .clone() @@ -805,17 +803,14 @@ impl LanguageRegistry { let adapter = language.adapter.clone()?; let lsp_binary_statuses = self.lsp_binary_statuses_tx.clone(); let login_shell_env_loaded = self.login_shell_env_loaded.clone(); - dbg!(); Some(cx.spawn(|cx| async move { login_shell_env_loaded.await; - let binary = this - .lsp_binary_paths - .lock() + let mut lock = this.lsp_binary_paths.lock(); + let entry = lock .entry(adapter.name.clone()) .or_insert_with(|| { - dbg!(); get_binary( adapter.clone(), language.clone(), @@ -827,10 +822,9 @@ impl LanguageRegistry { .boxed() .shared() }) - .clone() - .map_err(|e| anyhow!(e)) - .await?; - dbg!(); + .clone(); + drop(lock); + let binary = entry.clone().map_err(|e| anyhow!(e)).await?; let server = lsp::LanguageServer::new( server_id, @@ -840,7 +834,6 @@ impl LanguageRegistry { cx, )?; - dbg!(); Ok(server) })) } @@ -892,6 +885,7 @@ async fn get_binary( statuses.clone(), ) .await; + if let Err(error) = binary.as_ref() { if let Some(cached) = adapter.cached_server_binary(container_dir).await { statuses diff --git a/crates/zed/src/languages/node_runtime.rs b/crates/zed/src/languages/node_runtime.rs index 7f3b138f2a40936e7736a542083e093f275cc710..41cbefbb732fc14fd70397213dde450582209716 100644 --- a/crates/zed/src/languages/node_runtime.rs +++ b/crates/zed/src/languages/node_runtime.rs @@ -62,6 +62,7 @@ impl NodeRuntime { .output() .await .context("failed to run npm info")?; + if !output.status.success() { Err(anyhow!( "failed to execute npm info:\nstdout: {:?}\nstderr: {:?}", @@ -69,6 +70,7 @@ impl NodeRuntime { String::from_utf8_lossy(&output.stderr) ))?; } + let mut info: NpmInfo = serde_json::from_slice(&output.stdout)?; info.dist_tags .latest From 350f8ed3043101e15aeedba17072235b32d42fba Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 28 Mar 2023 11:48:00 -0400 Subject: [PATCH 8/9] Download the JSON LSP package instead of our own bundled binary --- crates/zed/src/languages/json.rs | 106 +++++++++++++++---------------- 1 file changed, 51 insertions(+), 55 deletions(-) diff --git a/crates/zed/src/languages/json.rs b/crates/zed/src/languages/json.rs index 10665d96b61f67c9ed4e1b177f331795e7a10fce..479308f370e6268d25d2b7bfae04ed383432e4d2 100644 --- a/crates/zed/src/languages/json.rs +++ b/crates/zed/src/languages/json.rs @@ -1,21 +1,16 @@ -use super::{ - installation::{latest_github_release, GitHubLspBinaryVersion}, - node_runtime::NodeRuntime, -}; -use anyhow::{anyhow, Result}; -use async_compression::futures::bufread::GzipDecoder; +use super::node_runtime::NodeRuntime; +use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use client::http::HttpClient; use collections::HashMap; -use futures::{future::BoxFuture, io::BufReader, FutureExt, StreamExt}; +use futures::{future::BoxFuture, FutureExt, StreamExt}; use gpui::MutableAppContext; use language::{LanguageRegistry, LanguageServerBinary, LanguageServerName, LspAdapter}; use serde_json::json; use settings::{keymap_file_json_schema, settings_file_json_schema}; -use smol::fs::{self, File}; +use smol::fs; use std::{ any::Any, - env::consts, ffi::OsString, future, path::{Path, PathBuf}, @@ -24,8 +19,11 @@ use std::{ use theme::ThemeRegistry; use util::{paths, ResultExt, StaffMode}; +const SERVER_PATH: &'static str = + "node_modules/vscode-json-languageserver/bin/vscode-json-languageserver"; + fn server_binary_arguments(server_path: &Path) -> Vec { - dbg!(vec![server_path.into(), "--stdio".into()]) + vec![server_path.into(), "--stdio".into()] } pub struct JsonLspAdapter { @@ -56,55 +54,42 @@ impl LspAdapter for JsonLspAdapter { async fn fetch_latest_server_version( &self, - http: Arc, + _: Arc, ) -> Result> { - let release = latest_github_release("zed-industries/json-language-server", http).await?; - let asset_name = format!("json-language-server-darwin-{}.gz", consts::ARCH); - let asset = release - .assets - .iter() - .find(|asset| asset.name == asset_name) - .ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?; - let version = GitHubLspBinaryVersion { - name: release.name, - url: asset.browser_download_url.clone(), - }; - Ok(Box::new(version) as Box<_>) + Ok(Box::new( + self.node + .npm_package_latest_version("vscode-json-languageserver") + .await?, + ) as Box<_>) } async fn fetch_server_binary( &self, version: Box, - http: Arc, + _: Arc, container_dir: PathBuf, ) -> Result { - let version = version.downcast::().unwrap(); - let destination_path = container_dir.join(format!( - "json-language-server-{}-{}", - version.name, - consts::ARCH - )); - - if fs::metadata(&destination_path).await.is_err() { - let mut response = http - .get(&version.url, Default::default(), true) - .await - .map_err(|err| anyhow!("error downloading release: {}", err))?; - let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut())); - let mut file = File::create(&destination_path).await?; - futures::io::copy(decompressed_bytes, &mut file).await?; - fs::set_permissions( - &destination_path, - ::from_mode(0o755), - ) - .await?; + let version = version.downcast::().unwrap(); + let version_dir = container_dir.join(version.as_str()); + fs::create_dir_all(&version_dir) + .await + .context("failed to create version directory")?; + let server_path = version_dir.join(SERVER_PATH); + + if fs::metadata(&server_path).await.is_err() { + self.node + .npm_install_packages( + [("vscode-json-languageserver", version.as_str())], + &version_dir, + ) + .await?; if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() { while let Some(entry) = entries.next().await { if let Some(entry) = entry.log_err() { let entry_path = entry.path(); - if entry_path.as_path() != destination_path { - fs::remove_file(&entry_path).await.log_err(); + if entry_path.as_path() != version_dir { + fs::remove_dir_all(&entry_path).await.log_err(); } } } @@ -113,23 +98,34 @@ impl LspAdapter for JsonLspAdapter { Ok(LanguageServerBinary { path: self.node.binary_path().await?, - arguments: server_binary_arguments(&destination_path), + arguments: server_binary_arguments(&server_path), }) } async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { (|| async move { - let mut last = None; + let mut last_version_dir = None; let mut entries = fs::read_dir(&container_dir).await?; while let Some(entry) = entries.next().await { - last = Some(entry?.path()); + let entry = entry?; + if entry.file_type().await?.is_dir() { + last_version_dir = Some(entry.path()); + } + } + + let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?; + let server_path = last_version_dir.join(SERVER_PATH); + if server_path.exists() { + Ok(LanguageServerBinary { + path: self.node.binary_path().await?, + arguments: server_binary_arguments(&server_path), + }) + } else { + Err(anyhow!( + "missing executable in directory {:?}", + last_version_dir + )) } - anyhow::Ok(LanguageServerBinary { - path: self.node.binary_path().await?, - arguments: server_binary_arguments( - &last.ok_or_else(|| anyhow!("no cached binary"))?, - ), - }) })() .await .log_err() From ee3ac9c3441ae6291aac3ace6be8857a671af642 Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 28 Mar 2023 11:51:09 -0400 Subject: [PATCH 9/9] Rename `installation.rs` -> `github.rs` now that is all it concerns --- crates/zed/src/languages.rs | 2 +- crates/zed/src/languages/c.rs | 2 +- crates/zed/src/languages/elixir.rs | 2 +- crates/zed/src/languages/{installation.rs => github.rs} | 0 crates/zed/src/languages/go.rs | 2 +- crates/zed/src/languages/lua.rs | 2 +- crates/zed/src/languages/rust.rs | 2 +- 7 files changed, 6 insertions(+), 6 deletions(-) rename crates/zed/src/languages/{installation.rs => github.rs} (100%) diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 8eae806fdb9e825afb0a2cc6b3ddcd14573186c9..c49c77f076d73d344345926fb3129fe74f6cf8e7 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -9,9 +9,9 @@ use theme::ThemeRegistry; mod c; mod elixir; +mod github; mod go; mod html; -mod installation; mod json; mod language_plugin; mod lua; diff --git a/crates/zed/src/languages/c.rs b/crates/zed/src/languages/c.rs index 1799dc2973a3fdb5312625ab1571f10c1bbfbf25..906592fc2d4b92f947b3328caa5182e32614fb1e 100644 --- a/crates/zed/src/languages/c.rs +++ b/crates/zed/src/languages/c.rs @@ -1,4 +1,4 @@ -use super::installation::{latest_github_release, GitHubLspBinaryVersion}; +use super::github::{latest_github_release, GitHubLspBinaryVersion}; use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use client::http::HttpClient; diff --git a/crates/zed/src/languages/elixir.rs b/crates/zed/src/languages/elixir.rs index 75b6ff18b2e1515f7c0bb2f1d9fe6d4314f879ee..9f921a0c402c1d87f21e96aa3ecded5de55b1899 100644 --- a/crates/zed/src/languages/elixir.rs +++ b/crates/zed/src/languages/elixir.rs @@ -1,4 +1,4 @@ -use super::installation::{latest_github_release, GitHubLspBinaryVersion}; +use super::github::{latest_github_release, GitHubLspBinaryVersion}; use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use client::http::HttpClient; diff --git a/crates/zed/src/languages/installation.rs b/crates/zed/src/languages/github.rs similarity index 100% rename from crates/zed/src/languages/installation.rs rename to crates/zed/src/languages/github.rs diff --git a/crates/zed/src/languages/go.rs b/crates/zed/src/languages/go.rs index b887aa10174bcb60cdf73db92cc87c6bda9758cf..9af309839fedb17a47b98b2d45ffe7fa79b0d940 100644 --- a/crates/zed/src/languages/go.rs +++ b/crates/zed/src/languages/go.rs @@ -1,4 +1,4 @@ -use super::installation::latest_github_release; +use super::github::latest_github_release; use anyhow::{anyhow, Result}; use async_trait::async_trait; use client::http::HttpClient; diff --git a/crates/zed/src/languages/lua.rs b/crates/zed/src/languages/lua.rs index 37c3e36489022d8a2ff51ffaa9843ffbd8dcbc08..7ffdac5218cb92a6a50622ae91e30eb0d8a07ead 100644 --- a/crates/zed/src/languages/lua.rs +++ b/crates/zed/src/languages/lua.rs @@ -10,7 +10,7 @@ use language::{LanguageServerBinary, LanguageServerName}; use smol::fs; use util::{async_iife, ResultExt}; -use super::installation::{latest_github_release, GitHubLspBinaryVersion}; +use super::github::{latest_github_release, GitHubLspBinaryVersion}; #[derive(Copy, Clone)] pub struct LuaLspAdapter; diff --git a/crates/zed/src/languages/rust.rs b/crates/zed/src/languages/rust.rs index 2fe063ed5ee8b1ee6bdca66361e245ec34d61324..0f8e90d7b268a71712375f0fda0406f0745110d6 100644 --- a/crates/zed/src/languages/rust.rs +++ b/crates/zed/src/languages/rust.rs @@ -1,4 +1,4 @@ -use super::installation::{latest_github_release, GitHubLspBinaryVersion}; +use super::github::{latest_github_release, GitHubLspBinaryVersion}; use anyhow::{anyhow, Result}; use async_compression::futures::bufread::GzipDecoder; use async_trait::async_trait;