Detailed changes
@@ -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};
@@ -188,7 +186,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(),
@@ -29,6 +29,7 @@ use std::{
any::Any,
borrow::Cow,
cell::RefCell,
+ ffi::OsString,
fmt::Debug,
hash::Hash,
mem,
@@ -77,12 +78,23 @@ pub trait ToLspPosition {
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct LanguageServerName(pub Arc<str>);
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
+pub enum ServerExecutionKind {
+ Launch,
+ Node,
+}
+
+#[derive(Debug, Clone, Deserialize)]
+pub struct LanguageServerBinary {
+ pub path: PathBuf,
+ pub arguments: Vec<OsString>,
+}
+
/// 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<String>,
pub initialization_options: Option<Value>,
pub disk_based_diagnostic_sources: Vec<String>,
pub disk_based_diagnostics_progress_token: Option<String>,
@@ -93,7 +105,6 @@ pub struct CachedLspAdapter {
impl CachedLspAdapter {
pub async fn new(adapter: Arc<dyn LspAdapter>) -> Arc<Self> {
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 =
@@ -102,7 +113,6 @@ impl CachedLspAdapter {
Arc::new(CachedLspAdapter {
name,
- server_args,
initialization_options,
disk_based_diagnostic_sources,
disk_based_diagnostics_progress_token,
@@ -123,13 +133,16 @@ impl CachedLspAdapter {
version: Box<dyn 'static + Send + Any>,
http: Arc<dyn HttpClient>,
container_dir: PathBuf,
- ) -> Result<PathBuf> {
+ ) -> Result<LanguageServerBinary> {
self.adapter
.fetch_server_binary(version, http, container_dir)
.await
}
- pub async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<PathBuf> {
+ pub async fn cached_server_binary(
+ &self,
+ container_dir: PathBuf,
+ ) -> Option<LanguageServerBinary> {
self.adapter.cached_server_binary(container_dir).await
}
@@ -182,9 +195,9 @@ pub trait LspAdapter: 'static + Send + Sync {
version: Box<dyn 'static + Send + Any>,
http: Arc<dyn HttpClient>,
container_dir: PathBuf,
- ) -> Result<PathBuf>;
+ ) -> Result<LanguageServerBinary>;
- async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<PathBuf>;
+ async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<LanguageServerBinary>;
async fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
@@ -207,10 +220,6 @@ pub trait LspAdapter: 'static + Send + Sync {
None
}
- async fn server_args(&self) -> Vec<String> {
- Vec::new()
- }
-
async fn initialization_options(&self) -> Option<Value> {
None
}
@@ -488,7 +497,7 @@ pub struct LanguageRegistry {
lsp_binary_paths: Mutex<
HashMap<
LanguageServerName,
- Shared<BoxFuture<'static, Result<PathBuf, Arc<anyhow::Error>>>>,
+ Shared<BoxFuture<'static, Result<LanguageServerBinary, Arc<anyhow::Error>>>>,
>,
>,
executor: Option<Arc<Background>>,
@@ -794,14 +803,15 @@ 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();
+
Some(cx.spawn(|cx| async move {
login_shell_env_loaded.await;
- let server_binary_path = this
- .lsp_binary_paths
- .lock()
+
+ let mut lock = this.lsp_binary_paths.lock();
+ let entry = lock
.entry(adapter.name.clone())
.or_insert_with(|| {
- get_server_binary_path(
+ get_binary(
adapter.clone(),
language.clone(),
http_client,
@@ -812,18 +822,18 @@ impl LanguageRegistry {
.boxed()
.shared()
})
- .clone()
- .map_err(|e| anyhow!(e));
+ .clone();
+ drop(lock);
+ let binary = entry.clone().map_err(|e| anyhow!(e)).await?;
- 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,
+ &binary.path,
+ &binary.arguments,
&root_path,
cx,
)?;
+
Ok(server)
}))
}
@@ -853,13 +863,13 @@ impl Default for LanguageRegistry {
}
}
-async fn get_server_binary_path(
+async fn get_binary(
adapter: Arc<CachedLspAdapter>,
language: Arc<Language>,
http_client: Arc<dyn HttpClient>,
download_dir: Arc<Path>,
statuses: async_broadcast::Sender<(Arc<Language>, LanguageServerBinaryStatus)>,
-) -> Result<PathBuf> {
+) -> Result<LanguageServerBinary> {
let container_dir = download_dir.join(adapter.name.0.as_ref());
if !container_dir.exists() {
smol::fs::create_dir_all(&container_dir)
@@ -867,7 +877,7 @@ async fn get_server_binary_path(
.context("failed to create container directory")?;
}
- let path = fetch_latest_server_binary_path(
+ let binary = fetch_latest_binary(
adapter.clone(),
language.clone(),
http_client,
@@ -875,12 +885,13 @@ 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((
@@ -892,16 +903,16 @@ async fn get_server_binary_path(
.await?;
}
}
- path
+ binary
}
-async fn fetch_latest_server_binary_path(
+async fn fetch_latest_binary(
adapter: Arc<CachedLspAdapter>,
language: Arc<Language>,
http_client: Arc<dyn HttpClient>,
container_dir: &Path,
lsp_binary_statuses_tx: async_broadcast::Sender<(Arc<Language>, LanguageServerBinaryStatus)>,
-) -> Result<PathBuf> {
+) -> Result<LanguageServerBinary> {
let container_dir: Arc<Path> = container_dir.into();
lsp_binary_statuses_tx
.broadcast((
@@ -915,13 +926,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 {
@@ -1454,11 +1465,11 @@ impl LspAdapter for Arc<FakeLspAdapter> {
_: Box<dyn 'static + Send + Any>,
_: Arc<dyn HttpClient>,
_: PathBuf,
- ) -> Result<PathBuf> {
+ ) -> Result<LanguageServerBinary> {
unreachable!();
}
- async fn cached_server_binary(&self, _: PathBuf) -> Option<PathBuf> {
+ async fn cached_server_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
unreachable!();
}
@@ -1516,7 +1527,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(
@@ -108,7 +108,7 @@ impl LanguageServer {
pub fn new<T: AsRef<std::ffi::OsStr>>(
server_id: usize,
binary_path: &Path,
- args: &[T],
+ arguments: &[T],
root_path: &Path,
cx: AsyncAppContext,
) -> Result<Self> {
@@ -117,9 +117,10 @@ impl LanguageServer {
} else {
root_path.parent().unwrap_or_else(|| Path::new("/"))
};
+
let mut server = process::Command::new(binary_path)
.current_dir(working_dir)
- .args(args)
+ .args(arguments)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
@@ -128,7 +129,6 @@ impl LanguageServer {
let stdin = server.stdin.take().unwrap();
let stout = server.stdout.take().unwrap();
-
let mut server = Self::new_internal(
server_id,
stdin,
@@ -147,6 +147,7 @@ impl LanguageServer {
);
},
);
+
if let Some(name) = binary_path.file_name() {
server.name = name.to_string_lossy().to_string();
}
@@ -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");
@@ -1,17 +1,21 @@
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;
mod c;
mod elixir;
+mod github;
mod go;
mod html;
-mod installation;
mod json;
mod language_plugin;
mod lua;
+mod node_runtime;
mod python;
mod ruby;
mod rust;
@@ -32,7 +36,14 @@ mod yaml;
#[exclude = "*.rs"]
struct LanguageDir;
-pub fn init(languages: Arc<LanguageRegistry>, themes: Arc<ThemeRegistry>) {
+pub fn init(
+ http: Arc<dyn HttpClient>,
+ background: Arc<Background>,
+ languages: Arc<LanguageRegistry>,
+ themes: Arc<ThemeRegistry>,
+) {
+ let node_runtime = NodeRuntime::new(http, background);
+
for (name, grammar, lsp_adapter) in [
(
"c",
@@ -63,6 +74,7 @@ pub fn init(languages: Arc<LanguageRegistry>, themes: Arc<ThemeRegistry>) {
"json",
tree_sitter_json::language(),
Some(Arc::new(json::JsonLspAdapter::new(
+ node_runtime.clone(),
languages.clone(),
themes.clone(),
))),
@@ -75,7 +87,9 @@ pub fn init(languages: Arc<LanguageRegistry>, themes: Arc<ThemeRegistry>) {
(
"python",
tree_sitter_python::language(),
- Some(Arc::new(python::PythonLspAdapter)),
+ Some(Arc::new(python::PythonLspAdapter::new(
+ node_runtime.clone(),
+ ))),
),
(
"rust",
@@ -90,22 +104,28 @@ pub fn init(languages: Arc<LanguageRegistry>, themes: Arc<ThemeRegistry>) {
(
"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",
@@ -135,7 +155,7 @@ pub fn init(languages: Arc<LanguageRegistry>, themes: Arc<ThemeRegistry>) {
(
"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);
@@ -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;
@@ -39,7 +39,7 @@ impl super::LspAdapter for CLspAdapter {
version: Box<dyn 'static + Send + Any>,
http: Arc<dyn HttpClient>,
container_dir: PathBuf,
- ) -> Result<PathBuf> {
+ ) -> Result<LanguageServerBinary> {
let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
let zip_path = container_dir.join(format!("clangd_{}.zip", version.name));
let version_dir = container_dir.join(format!("clangd_{}", version.name));
@@ -81,10 +81,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<PathBuf> {
+ async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<LanguageServerBinary> {
(|| async move {
let mut last_clangd_dir = None;
let mut entries = fs::read_dir(&container_dir).await?;
@@ -97,7 +100,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 {:?}",
@@ -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;
@@ -40,7 +40,7 @@ impl LspAdapter for ElixirLspAdapter {
version: Box<dyn 'static + Send + Any>,
http: Arc<dyn HttpClient>,
container_dir: PathBuf,
- ) -> Result<PathBuf> {
+ ) -> Result<LanguageServerBinary> {
let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
let zip_path = container_dir.join(format!("elixir-ls_{}.zip", version.name));
let version_dir = container_dir.join(format!("elixir-ls_{}", version.name));
@@ -94,17 +94,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<PathBuf> {
+ async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<LanguageServerBinary> {
(|| 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()
@@ -0,0 +1,45 @@
+use anyhow::{Context, Result};
+use client::http::HttpClient;
+use serde::Deserialize;
+use smol::io::AsyncReadExt;
+use std::sync::Arc;
+
+pub struct GitHubLspBinaryVersion {
+ pub name: String,
+ pub url: String,
+}
+
+#[derive(Deserialize)]
+pub(crate) struct GithubRelease {
+ pub name: String,
+ pub assets: Vec<GithubReleaseAsset>,
+}
+
+#[derive(Deserialize)]
+pub(crate) struct GithubReleaseAsset {
+ pub name: String,
+ pub browser_download_url: String,
+}
+
+pub(crate) async fn latest_github_release(
+ repo_name_with_owner: &str,
+ http: Arc<dyn HttpClient>,
+) -> Result<GithubRelease, anyhow::Error> {
+ let mut response = http
+ .get(
+ &format!("https://api.github.com/repos/{repo_name_with_owner}/releases/latest"),
+ Default::default(),
+ true,
+ )
+ .await
+ .context("error fetching latest release")?;
+ let mut body = Vec::new();
+ response
+ .body_mut()
+ .read_to_end(&mut body)
+ .await
+ .context("error reading latest release")?;
+ let release: GithubRelease =
+ serde_json::from_slice(body.as_slice()).context("error deserializing latest release")?;
+ Ok(release)
+}
@@ -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;
@@ -7,9 +7,13 @@ 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<OsString> {
+ vec!["-mode=stdio".into()]
+}
+
#[derive(Copy, Clone)]
pub struct GoLspAdapter;
@@ -23,10 +27,6 @@ impl super::LspAdapter for GoLspAdapter {
LanguageServerName("gopls".into())
}
- async fn server_args(&self) -> Vec<String> {
- vec!["-mode=stdio".into()]
- }
-
async fn fetch_latest_server_version(
&self,
http: Arc<dyn HttpClient>,
@@ -47,7 +47,7 @@ impl super::LspAdapter for GoLspAdapter {
version: Box<dyn 'static + Send + Any>,
_: Arc<dyn HttpClient>,
container_dir: PathBuf,
- ) -> Result<PathBuf> {
+ ) -> Result<LanguageServerBinary> {
let version = version.downcast::<Option<String>>().unwrap();
let this = *self;
@@ -68,7 +68,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 {
@@ -102,10 +105,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<PathBuf> {
+ async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<LanguageServerBinary> {
(|| async move {
let mut last_binary_path = None;
let mut entries = fs::read_dir(&container_dir).await?;
@@ -122,7 +128,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"))
}
@@ -1,19 +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::{LanguageServerName, LspAdapter};
+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;
-pub struct HtmlLspAdapter;
+fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
+ vec![server_path.into(), "--stdio".into()]
+}
+
+pub struct HtmlLspAdapter {
+ node: Arc<NodeRuntime>,
+}
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<NodeRuntime>) -> Self {
+ HtmlLspAdapter { node }
+ }
}
#[async_trait]
@@ -22,15 +37,15 @@ impl LspAdapter for HtmlLspAdapter {
LanguageServerName("vscode-html-language-server".into())
}
- async fn server_args(&self) -> Vec<String> {
- vec!["--stdio".into()]
- }
-
async fn fetch_latest_server_version(
&self,
_: Arc<dyn HttpClient>,
) -> Result<Box<dyn 'static + Any + Send>> {
- Ok(Box::new(npm_package_latest_version("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(
@@ -38,20 +53,21 @@ impl LspAdapter for HtmlLspAdapter {
version: Box<dyn 'static + Send + Any>,
_: Arc<dyn HttpClient>,
container_dir: PathBuf,
- ) -> Result<PathBuf> {
+ ) -> Result<LanguageServerBinary> {
let version = version.downcast::<String>().unwrap();
let version_dir = container_dir.join(version.as_str());
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(
- [("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 {
@@ -65,10 +81,13 @@ impl LspAdapter for HtmlLspAdapter {
}
}
- Ok(binary_path)
+ Ok(LanguageServerBinary {
+ path: self.node.binary_path().await?,
+ arguments: server_binary_arguments(&server_path),
+ })
}
- async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<PathBuf> {
+ async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<LanguageServerBinary> {
(|| async move {
let mut last_version_dir = None;
let mut entries = fs::read_dir(&container_dir).await?;
@@ -79,9 +98,12 @@ 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)
+ let server_path = last_version_dir.join(Self::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 {:?}",
@@ -1,109 +0,0 @@
-use anyhow::{anyhow, Context, Result};
-use client::http::HttpClient;
-
-use serde::Deserialize;
-use smol::io::AsyncReadExt;
-use std::{path::Path, 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<String>,
-}
-
-#[derive(Deserialize, Default)]
-struct NpmInfoDistTags {
- latest: Option<String>,
-}
-
-#[derive(Deserialize)]
-pub(crate) struct GithubRelease {
- pub name: String,
- pub assets: Vec<GithubReleaseAsset>,
-}
-
-#[derive(Deserialize)]
-pub(crate) struct GithubReleaseAsset {
- pub name: String,
- pub browser_download_url: String,
-}
-
-pub async fn npm_package_latest_version(name: &str) -> Result<String> {
- let output = smol::process::Command::new("npm")
- .args(["-fetch-retry-mintimeout", "2000"])
- .args(["-fetch-retry-maxtimeout", "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(
- packages: impl IntoIterator<Item = (&str, &str)>,
- directory: &Path,
-) -> Result<()> {
- let output = smol::process::Command::new("npm")
- .args(["-fetch-retry-mintimeout", "2000"])
- .args(["-fetch-retry-maxtimeout", "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<dyn HttpClient>,
-) -> Result<GithubRelease, anyhow::Error> {
- let mut response = http
- .get(
- &format!("https://api.github.com/repos/{repo_name_with_owner}/releases/latest"),
- Default::default(),
- true,
- )
- .await
- .context("error fetching latest release")?;
- let mut body = Vec::new();
- response
- .body_mut()
- .read_to_end(&mut body)
- .await
- .context("error reading latest release")?;
- let release: GithubRelease =
- serde_json::from_slice(body.as_slice()).context("error deserializing latest release")?;
- Ok(release)
-}
@@ -1,18 +1,17 @@
-use super::installation::{latest_github_release, GitHubLspBinaryVersion};
-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, LanguageServerName, LspAdapter};
+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},
sync::Arc,
@@ -20,14 +19,30 @@ 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<OsString> {
+ vec![server_path.into(), "--stdio".into()]
+}
+
pub struct JsonLspAdapter {
+ node: Arc<NodeRuntime>,
languages: Arc<LanguageRegistry>,
themes: Arc<ThemeRegistry>,
}
impl JsonLspAdapter {
- pub fn new(languages: Arc<LanguageRegistry>, themes: Arc<ThemeRegistry>) -> Self {
- Self { languages, themes }
+ pub fn new(
+ node: Arc<NodeRuntime>,
+ languages: Arc<LanguageRegistry>,
+ themes: Arc<ThemeRegistry>,
+ ) -> Self {
+ JsonLspAdapter {
+ node,
+ languages,
+ themes,
+ }
}
}
@@ -37,78 +52,80 @@ impl LspAdapter for JsonLspAdapter {
LanguageServerName("json-language-server".into())
}
- async fn server_args(&self) -> Vec<String> {
- vec!["--stdio".into()]
- }
-
async fn fetch_latest_server_version(
&self,
- http: Arc<dyn HttpClient>,
+ _: Arc<dyn HttpClient>,
) -> Result<Box<dyn 'static + Send + Any>> {
- 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<dyn 'static + Send + Any>,
- http: Arc<dyn HttpClient>,
+ _: Arc<dyn HttpClient>,
container_dir: PathBuf,
- ) -> Result<PathBuf> {
- let version = version.downcast::<GitHubLspBinaryVersion>().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,
- <fs::Permissions as fs::unix::PermissionsExt>::from_mode(0o755),
- )
- .await?;
+ ) -> Result<LanguageServerBinary> {
+ let version = version.downcast::<String>().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();
}
}
}
}
}
- Ok(destination_path)
+ Ok(LanguageServerBinary {
+ path: self.node.binary_path().await?,
+ arguments: server_binary_arguments(&server_path),
+ })
}
- async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<PathBuf> {
+ async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<LanguageServerBinary> {
(|| 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
+ ))
}
- last.ok_or_else(|| anyhow!("no cached binary"))
})()
.await
.log_err()
@@ -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::{LanguageServerBinary, LanguageServerName, LspAdapter};
use plugin_runtime::{Plugin, PluginBinary, PluginBuilder, WasiFn};
use std::{any::Any, path::PathBuf, sync::Arc};
use util::ResultExt;
@@ -32,10 +32,9 @@ pub async fn new_json(executor: Arc<Background>) -> Result<PluginLspAdapter> {
pub struct PluginLspAdapter {
name: WasiFn<(), String>,
- server_args: WasiFn<(), Vec<String>>,
fetch_latest_server_version: WasiFn<(), Option<String>>,
- fetch_server_binary: WasiFn<(PathBuf, String), Result<PathBuf, String>>,
- cached_server_binary: WasiFn<PathBuf, Option<PathBuf>>,
+ fetch_server_binary: WasiFn<(PathBuf, String), Result<LanguageServerBinary, String>>,
+ cached_server_binary: WasiFn<PathBuf, Option<LanguageServerBinary>>,
initialization_options: WasiFn<(), String>,
language_ids: WasiFn<(), Vec<(String, String)>>,
executor: Arc<Background>,
@@ -47,7 +46,6 @@ impl PluginLspAdapter {
pub async fn new(mut plugin: Plugin, executor: Arc<Background>) -> Result<Self> {
Ok(Self {
name: plugin.function("name")?,
- 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")?,
@@ -72,15 +70,6 @@ impl LspAdapter for PluginLspAdapter {
LanguageServerName(name.into())
}
- async fn server_args<'a>(&'a self) -> Vec<String> {
- self.runtime
- .lock()
- .await
- .call(&self.server_args, ())
- .await
- .unwrap()
- }
-
async fn fetch_latest_server_version(
&self,
_: Arc<dyn HttpClient>,
@@ -105,7 +94,7 @@ impl LspAdapter for PluginLspAdapter {
version: Box<dyn 'static + Send + Any>,
_: Arc<dyn HttpClient>,
container_dir: PathBuf,
- ) -> Result<PathBuf> {
+ ) -> Result<LanguageServerBinary> {
let version = *version.downcast::<String>().unwrap();
let runtime = self.runtime.clone();
let function = self.fetch_server_binary;
@@ -113,7 +102,7 @@ impl LspAdapter for PluginLspAdapter {
.spawn(async move {
let mut runtime = runtime.lock().await;
let handle = runtime.attach_path(&container_dir)?;
- let result: Result<PathBuf, String> =
+ let result: Result<LanguageServerBinary, String> =
runtime.call(&function, (container_dir, version)).await?;
runtime.remove_resource(handle)?;
result.map_err(|e| anyhow!("{}", e))
@@ -121,7 +110,7 @@ impl LspAdapter for PluginLspAdapter {
.await
}
- async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<PathBuf> {
+ async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<LanguageServerBinary> {
let runtime = self.runtime.clone();
let function = self.cached_server_binary;
@@ -129,7 +118,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<PathBuf> = runtime.call(&function, container_dir).await.ok()?;
+ let result: Option<LanguageServerBinary> =
+ runtime.call(&function, container_dir).await.ok()?;
runtime.remove_resource(handle).ok()?;
result
})
@@ -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,28 +6,28 @@ use async_tar::Archive;
use async_trait::async_trait;
use client::http::HttpClient;
use futures::{io::BufReader, StreamExt};
-use language::LanguageServerName;
+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;
+fn server_binary_arguments() -> Vec<OsString> {
+ vec![
+ "--logpath=~/lua-language-server.log".into(),
+ "--loglevel=trace".into(),
+ ]
+}
+
#[async_trait]
impl super::LspAdapter for LuaLspAdapter {
async fn name(&self) -> LanguageServerName {
LanguageServerName("lua-language-server".into())
}
- async fn server_args(&self) -> Vec<String> {
- vec![
- "--logpath=~/lua-language-server.log".into(),
- "--loglevel=trace".into(),
- ]
- }
-
async fn fetch_latest_server_version(
&self,
http: Arc<dyn HttpClient>,
@@ -57,7 +57,7 @@ impl super::LspAdapter for LuaLspAdapter {
version: Box<dyn 'static + Send + Any>,
http: Arc<dyn HttpClient>,
container_dir: PathBuf,
- ) -> Result<PathBuf> {
+ ) -> Result<LanguageServerBinary> {
let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
let binary_path = container_dir.join("bin/lua-language-server");
@@ -77,10 +77,13 @@ impl super::LspAdapter for LuaLspAdapter {
<fs::Permissions as fs::unix::PermissionsExt>::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<PathBuf> {
+ async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<LanguageServerBinary> {
async_iife!({
let mut last_binary_path = None;
let mut entries = fs::read_dir(&container_dir).await?;
@@ -97,7 +100,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"))
}
@@ -0,0 +1,166 @@
+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};
+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 {
+ #[serde(default)]
+ dist_tags: NpmInfoDistTags,
+ versions: Vec<String>,
+}
+
+#[derive(Deserialize, Default)]
+pub struct NpmInfoDistTags {
+ latest: Option<String>,
+}
+
+pub struct NodeRuntime {
+ http: Arc<dyn HttpClient>,
+ background: Arc<Background>,
+ installation_path: Mutex<Option<Shared<Task<Result<PathBuf, Arc<anyhow::Error>>>>>>,
+}
+
+impl NodeRuntime {
+ pub fn new(http: Arc<dyn HttpClient>, background: Arc<Background>) -> Arc<NodeRuntime> {
+ Arc::new(NodeRuntime {
+ http,
+ background,
+ installation_path: Mutex::new(None),
+ })
+ }
+
+ pub async fn binary_path(&self) -> Result<PathBuf> {
+ 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<String> {
+ 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<Item = (&str, &str)>,
+ 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<PathBuf> {
+ let task = self
+ .installation_path
+ .lock()
+ .get_or_insert_with(|| {
+ let http = self.http.clone();
+ self.background
+ .spawn(async move { Self::install(http).await.map_err(Arc::new) })
+ .shared()
+ })
+ .clone();
+
+ match task.await {
+ Ok(path) => Ok(path),
+ Err(error) => Err(anyhow!("{}", error)),
+ }
+ }
+
+ async fn install(http: Arc<dyn HttpClient>) -> Result<PathBuf> {
+ 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)
+ }
+}
@@ -1,17 +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::{LanguageServerName, LspAdapter};
+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<OsString> {
+ vec![server_path.into(), "--stdio".into()]
+}
+
+pub struct PythonLspAdapter {
+ node: Arc<NodeRuntime>,
+}
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<NodeRuntime>) -> Self {
+ PythonLspAdapter { node }
+ }
}
#[async_trait]
@@ -20,15 +35,11 @@ impl LspAdapter for PythonLspAdapter {
LanguageServerName("pyright".into())
}
- async fn server_args(&self) -> Vec<String> {
- vec!["--stdio".into()]
- }
-
async fn fetch_latest_server_version(
&self,
_: Arc<dyn HttpClient>,
) -> Result<Box<dyn 'static + Any + Send>> {
- Ok(Box::new(npm_package_latest_version("pyright").await?) as Box<_>)
+ Ok(Box::new(self.node.npm_package_latest_version("pyright").await?) as Box<_>)
}
async fn fetch_server_binary(
@@ -36,16 +47,18 @@ impl LspAdapter for PythonLspAdapter {
version: Box<dyn 'static + Send + Any>,
_: Arc<dyn HttpClient>,
container_dir: PathBuf,
- ) -> Result<PathBuf> {
+ ) -> Result<LanguageServerBinary> {
let version = version.downcast::<String>().unwrap();
let version_dir = container_dir.join(version.as_str());
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([("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 {
@@ -59,10 +72,13 @@ impl LspAdapter for PythonLspAdapter {
}
}
- Ok(binary_path)
+ Ok(LanguageServerBinary {
+ path: self.node.binary_path().await?,
+ arguments: server_binary_arguments(&server_path),
+ })
}
- async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<PathBuf> {
+ async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<LanguageServerBinary> {
(|| async move {
let mut last_version_dir = None;
let mut entries = fs::read_dir(&container_dir).await?;
@@ -73,9 +89,12 @@ 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)
+ let server_path = last_version_dir.join(Self::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 {:?}",
@@ -1,7 +1,7 @@
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use client::http::HttpClient;
-use language::{LanguageServerName, LspAdapter};
+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_args(&self) -> Vec<String> {
- vec!["stdio".into()]
- }
-
async fn fetch_latest_server_version(
&self,
_: Arc<dyn HttpClient>,
@@ -28,12 +24,15 @@ impl LspAdapter for RubyLanguageServer {
_version: Box<dyn 'static + Send + Any>,
_: Arc<dyn HttpClient>,
_container_dir: PathBuf,
- ) -> Result<PathBuf> {
+ ) -> Result<LanguageServerBinary> {
Err(anyhow!("solargraph must be installed manually"))
}
- async fn cached_server_binary(&self, _container_dir: PathBuf) -> Option<PathBuf> {
- Some("solargraph".into())
+ async fn cached_server_binary(&self, _container_dir: PathBuf) -> Option<LanguageServerBinary> {
+ Some(LanguageServerBinary {
+ path: "solargraph".into(),
+ arguments: vec!["stdio".into()],
+ })
}
async fn label_for_completion(
@@ -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;
@@ -42,7 +42,7 @@ impl LspAdapter for RustLspAdapter {
version: Box<dyn 'static + Send + Any>,
http: Arc<dyn HttpClient>,
container_dir: PathBuf,
- ) -> Result<PathBuf> {
+ ) -> Result<LanguageServerBinary> {
let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
let destination_path = container_dir.join(format!("rust-analyzer-{}", version.name));
@@ -72,17 +72,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<PathBuf> {
+ async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<LanguageServerBinary> {
(|| 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()
@@ -1,19 +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::{LanguageServerName, LspAdapter};
+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;
-pub struct TypeScriptLspAdapter;
+fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
+ vec![
+ server_path.into(),
+ "--stdio".into(),
+ "--tsserver-path".into(),
+ "node_modules/typescript/lib".into(),
+ ]
+}
+
+pub struct TypeScriptLspAdapter {
+ node: Arc<NodeRuntime>,
+}
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<NodeRuntime>) -> Self {
+ TypeScriptLspAdapter { node }
+ }
}
struct Versions {
@@ -27,20 +47,16 @@ impl LspAdapter for TypeScriptLspAdapter {
LanguageServerName("typescript-language-server".into())
}
- async fn server_args(&self) -> Vec<String> {
- ["--stdio", "--tsserver-path", "node_modules/typescript/lib"]
- .into_iter()
- .map(str::to_string)
- .collect()
- }
-
async fn fetch_latest_server_version(
&self,
_: Arc<dyn HttpClient>,
) -> Result<Box<dyn 'static + Send + Any>> {
Ok(Box::new(Versions {
- typescript_version: npm_package_latest_version("typescript").await?,
- server_version: npm_package_latest_version("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<_>)
}
@@ -49,7 +65,7 @@ impl LspAdapter for TypeScriptLspAdapter {
versions: Box<dyn 'static + Send + Any>,
_: Arc<dyn HttpClient>,
container_dir: PathBuf,
- ) -> Result<PathBuf> {
+ ) -> Result<LanguageServerBinary> {
let versions = versions.downcast::<Versions>().unwrap();
let version_dir = container_dir.join(&format!(
"typescript-{}:server-{}",
@@ -58,20 +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(
- [
- ("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 {
@@ -85,10 +102,13 @@ impl LspAdapter for TypeScriptLspAdapter {
}
}
- Ok(binary_path)
+ Ok(LanguageServerBinary {
+ path: self.node.binary_path().await?,
+ arguments: server_binary_arguments(&server_path),
+ })
}
- async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<PathBuf> {
+ async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<LanguageServerBinary> {
(|| async move {
let mut last_version_dir = None;
let mut entries = fs::read_dir(&container_dir).await?;
@@ -99,12 +119,18 @@ 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() {
- Ok(new_bin_path)
- } else if old_bin_path.exists() {
- Ok(old_bin_path)
+ 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: self.node.binary_path().await?,
+ arguments: server_binary_arguments(&new_server_path),
+ })
+ } else if old_server_path.exists() {
+ Ok(LanguageServerBinary {
+ path: self.node.binary_path().await?,
+ arguments: server_binary_arguments(&old_server_path),
+ })
} else {
Err(anyhow!(
"missing executable in directory {:?}",
@@ -1,21 +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::{LanguageServerName, LspAdapter};
+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(server_path: &Path) -> Vec<OsString> {
+ vec![server_path.into(), "--stdio".into()]
+}
-pub struct YamlLspAdapter;
+pub struct YamlLspAdapter {
+ node: Arc<NodeRuntime>,
+}
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<NodeRuntime>) -> Self {
+ YamlLspAdapter { node }
+ }
}
#[async_trait]
@@ -24,15 +39,15 @@ impl LspAdapter for YamlLspAdapter {
LanguageServerName("yaml-language-server".into())
}
- async fn server_args(&self) -> Vec<String> {
- vec!["--stdio".into()]
- }
-
async fn fetch_latest_server_version(
&self,
_: Arc<dyn HttpClient>,
) -> Result<Box<dyn 'static + Any + Send>> {
- Ok(Box::new(npm_package_latest_version("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(
@@ -40,16 +55,17 @@ impl LspAdapter for YamlLspAdapter {
version: Box<dyn 'static + Send + Any>,
_: Arc<dyn HttpClient>,
container_dir: PathBuf,
- ) -> Result<PathBuf> {
+ ) -> Result<LanguageServerBinary> {
let version = version.downcast::<String>().unwrap();
let version_dir = container_dir.join(version.as_str());
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([("yaml-language-server", version.as_str())], &version_dir)
+ 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() {
@@ -64,10 +80,13 @@ impl LspAdapter for YamlLspAdapter {
}
}
- Ok(binary_path)
+ Ok(LanguageServerBinary {
+ path: self.node.binary_path().await?,
+ arguments: server_binary_arguments(&server_path),
+ })
}
- async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<PathBuf> {
+ async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<LanguageServerBinary> {
(|| async move {
let mut last_version_dir = None;
let mut entries = fs::read_dir(&container_dir).await?;
@@ -78,9 +97,12 @@ 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)
+ let server_path = last_version_dir.join(Self::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 {:?}",
@@ -139,7 +139,12 @@ fn main() {
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());
@@ -652,9 +652,10 @@ 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, Task, TestAppContext, ViewHandle,
+ executor::Deterministic, AssetSource, MutableAppContext, TestAppContext, ViewHandle,
};
use language::LanguageRegistry;
use project::{Project, ProjectPath};
@@ -1846,11 +1847,16 @@ 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());
- 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);
}
@@ -6,7 +6,7 @@ use serde::Deserialize;
#[import]
fn command(string: &str) -> Option<Vec<u8>>;
-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<Pa
let version_dir = container_dir.join(version.as_str());
fs::create_dir_all(&version_dir)
.map_err(|_| "failed to create version directory".to_string())?;
- let binary_path = version_dir.join(BIN_PATH);
+ let binary_path = version_dir.join(SERVER_PATH);
if fs::metadata(&binary_path).is_err() {
let output = command(&format!(
@@ -76,9 +76,9 @@ pub fn cached_server_binary(container_dir: PathBuf) -> Option<PathBuf> {
}
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