Merge pull request #2324 from zed-industries/download-node
Automatically download Node for Node based language servers
This commit is contained in:
commit
35b2aceffb
23 changed files with 660 additions and 387 deletions
|
@ -13,9 +13,7 @@ use client::{
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
use fs::FakeFs;
|
use fs::FakeFs;
|
||||||
use futures::{channel::oneshot, StreamExt as _};
|
use futures::{channel::oneshot, StreamExt as _};
|
||||||
use gpui::{
|
use gpui::{executor::Deterministic, test::EmptyView, ModelHandle, TestAppContext, ViewHandle};
|
||||||
executor::Deterministic, test::EmptyView, ModelHandle, Task, TestAppContext, ViewHandle,
|
|
||||||
};
|
|
||||||
use language::LanguageRegistry;
|
use language::LanguageRegistry;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use project::{Project, WorktreeId};
|
use project::{Project, WorktreeId};
|
||||||
|
@ -188,7 +186,7 @@ impl TestServer {
|
||||||
let app_state = Arc::new(workspace::AppState {
|
let app_state = Arc::new(workspace::AppState {
|
||||||
client: client.clone(),
|
client: client.clone(),
|
||||||
user_store: user_store.clone(),
|
user_store: user_store.clone(),
|
||||||
languages: Arc::new(LanguageRegistry::new(Task::ready(()))),
|
languages: Arc::new(LanguageRegistry::test()),
|
||||||
themes: ThemeRegistry::new((), cx.font_cache()),
|
themes: ThemeRegistry::new((), cx.font_cache()),
|
||||||
fs: fs.clone(),
|
fs: fs.clone(),
|
||||||
build_window_options: |_, _, _| Default::default(),
|
build_window_options: |_, _, _| Default::default(),
|
||||||
|
|
|
@ -29,6 +29,7 @@ use std::{
|
||||||
any::Any,
|
any::Any,
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
|
ffi::OsString,
|
||||||
fmt::Debug,
|
fmt::Debug,
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
mem,
|
mem,
|
||||||
|
@ -77,12 +78,23 @@ pub trait ToLspPosition {
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct LanguageServerName(pub Arc<str>);
|
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.
|
/// Represents a Language Server, with certain cached sync properties.
|
||||||
/// Uses [`LspAdapter`] under the hood, but calls all 'static' methods
|
/// Uses [`LspAdapter`] under the hood, but calls all 'static' methods
|
||||||
/// once at startup, and caches the results.
|
/// once at startup, and caches the results.
|
||||||
pub struct CachedLspAdapter {
|
pub struct CachedLspAdapter {
|
||||||
pub name: LanguageServerName,
|
pub name: LanguageServerName,
|
||||||
pub server_args: Vec<String>,
|
|
||||||
pub initialization_options: Option<Value>,
|
pub initialization_options: Option<Value>,
|
||||||
pub disk_based_diagnostic_sources: Vec<String>,
|
pub disk_based_diagnostic_sources: Vec<String>,
|
||||||
pub disk_based_diagnostics_progress_token: Option<String>,
|
pub disk_based_diagnostics_progress_token: Option<String>,
|
||||||
|
@ -93,7 +105,6 @@ pub struct CachedLspAdapter {
|
||||||
impl CachedLspAdapter {
|
impl CachedLspAdapter {
|
||||||
pub async fn new(adapter: Arc<dyn LspAdapter>) -> Arc<Self> {
|
pub async fn new(adapter: Arc<dyn LspAdapter>) -> Arc<Self> {
|
||||||
let name = adapter.name().await;
|
let name = adapter.name().await;
|
||||||
let server_args = adapter.server_args().await;
|
|
||||||
let initialization_options = adapter.initialization_options().await;
|
let initialization_options = adapter.initialization_options().await;
|
||||||
let disk_based_diagnostic_sources = adapter.disk_based_diagnostic_sources().await;
|
let disk_based_diagnostic_sources = adapter.disk_based_diagnostic_sources().await;
|
||||||
let disk_based_diagnostics_progress_token =
|
let disk_based_diagnostics_progress_token =
|
||||||
|
@ -102,7 +113,6 @@ impl CachedLspAdapter {
|
||||||
|
|
||||||
Arc::new(CachedLspAdapter {
|
Arc::new(CachedLspAdapter {
|
||||||
name,
|
name,
|
||||||
server_args,
|
|
||||||
initialization_options,
|
initialization_options,
|
||||||
disk_based_diagnostic_sources,
|
disk_based_diagnostic_sources,
|
||||||
disk_based_diagnostics_progress_token,
|
disk_based_diagnostics_progress_token,
|
||||||
|
@ -123,13 +133,16 @@ impl CachedLspAdapter {
|
||||||
version: Box<dyn 'static + Send + Any>,
|
version: Box<dyn 'static + Send + Any>,
|
||||||
http: Arc<dyn HttpClient>,
|
http: Arc<dyn HttpClient>,
|
||||||
container_dir: PathBuf,
|
container_dir: PathBuf,
|
||||||
) -> Result<PathBuf> {
|
) -> Result<LanguageServerBinary> {
|
||||||
self.adapter
|
self.adapter
|
||||||
.fetch_server_binary(version, http, container_dir)
|
.fetch_server_binary(version, http, container_dir)
|
||||||
.await
|
.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
|
self.adapter.cached_server_binary(container_dir).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,9 +195,9 @@ pub trait LspAdapter: 'static + Send + Sync {
|
||||||
version: Box<dyn 'static + Send + Any>,
|
version: Box<dyn 'static + Send + Any>,
|
||||||
http: Arc<dyn HttpClient>,
|
http: Arc<dyn HttpClient>,
|
||||||
container_dir: PathBuf,
|
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) {}
|
async fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
|
||||||
|
|
||||||
|
@ -207,10 +220,6 @@ pub trait LspAdapter: 'static + Send + Sync {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn server_args(&self) -> Vec<String> {
|
|
||||||
Vec::new()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn initialization_options(&self) -> Option<Value> {
|
async fn initialization_options(&self) -> Option<Value> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -488,7 +497,7 @@ pub struct LanguageRegistry {
|
||||||
lsp_binary_paths: Mutex<
|
lsp_binary_paths: Mutex<
|
||||||
HashMap<
|
HashMap<
|
||||||
LanguageServerName,
|
LanguageServerName,
|
||||||
Shared<BoxFuture<'static, Result<PathBuf, Arc<anyhow::Error>>>>,
|
Shared<BoxFuture<'static, Result<LanguageServerBinary, Arc<anyhow::Error>>>>,
|
||||||
>,
|
>,
|
||||||
>,
|
>,
|
||||||
executor: Option<Arc<Background>>,
|
executor: Option<Arc<Background>>,
|
||||||
|
@ -794,14 +803,15 @@ impl LanguageRegistry {
|
||||||
let adapter = language.adapter.clone()?;
|
let adapter = language.adapter.clone()?;
|
||||||
let lsp_binary_statuses = self.lsp_binary_statuses_tx.clone();
|
let lsp_binary_statuses = self.lsp_binary_statuses_tx.clone();
|
||||||
let login_shell_env_loaded = self.login_shell_env_loaded.clone();
|
let login_shell_env_loaded = self.login_shell_env_loaded.clone();
|
||||||
|
|
||||||
Some(cx.spawn(|cx| async move {
|
Some(cx.spawn(|cx| async move {
|
||||||
login_shell_env_loaded.await;
|
login_shell_env_loaded.await;
|
||||||
let server_binary_path = this
|
|
||||||
.lsp_binary_paths
|
let mut lock = this.lsp_binary_paths.lock();
|
||||||
.lock()
|
let entry = lock
|
||||||
.entry(adapter.name.clone())
|
.entry(adapter.name.clone())
|
||||||
.or_insert_with(|| {
|
.or_insert_with(|| {
|
||||||
get_server_binary_path(
|
get_binary(
|
||||||
adapter.clone(),
|
adapter.clone(),
|
||||||
language.clone(),
|
language.clone(),
|
||||||
http_client,
|
http_client,
|
||||||
|
@ -812,18 +822,18 @@ impl LanguageRegistry {
|
||||||
.boxed()
|
.boxed()
|
||||||
.shared()
|
.shared()
|
||||||
})
|
})
|
||||||
.clone()
|
.clone();
|
||||||
.map_err(|e| anyhow!(e));
|
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(
|
let server = lsp::LanguageServer::new(
|
||||||
server_id,
|
server_id,
|
||||||
&server_binary_path,
|
&binary.path,
|
||||||
server_args,
|
&binary.arguments,
|
||||||
&root_path,
|
&root_path,
|
||||||
cx,
|
cx,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(server)
|
Ok(server)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -853,13 +863,13 @@ impl Default for LanguageRegistry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_server_binary_path(
|
async fn get_binary(
|
||||||
adapter: Arc<CachedLspAdapter>,
|
adapter: Arc<CachedLspAdapter>,
|
||||||
language: Arc<Language>,
|
language: Arc<Language>,
|
||||||
http_client: Arc<dyn HttpClient>,
|
http_client: Arc<dyn HttpClient>,
|
||||||
download_dir: Arc<Path>,
|
download_dir: Arc<Path>,
|
||||||
statuses: async_broadcast::Sender<(Arc<Language>, LanguageServerBinaryStatus)>,
|
statuses: async_broadcast::Sender<(Arc<Language>, LanguageServerBinaryStatus)>,
|
||||||
) -> Result<PathBuf> {
|
) -> Result<LanguageServerBinary> {
|
||||||
let container_dir = download_dir.join(adapter.name.0.as_ref());
|
let container_dir = download_dir.join(adapter.name.0.as_ref());
|
||||||
if !container_dir.exists() {
|
if !container_dir.exists() {
|
||||||
smol::fs::create_dir_all(&container_dir)
|
smol::fs::create_dir_all(&container_dir)
|
||||||
|
@ -867,7 +877,7 @@ async fn get_server_binary_path(
|
||||||
.context("failed to create container directory")?;
|
.context("failed to create container directory")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let path = fetch_latest_server_binary_path(
|
let binary = fetch_latest_binary(
|
||||||
adapter.clone(),
|
adapter.clone(),
|
||||||
language.clone(),
|
language.clone(),
|
||||||
http_client,
|
http_client,
|
||||||
|
@ -875,12 +885,13 @@ async fn get_server_binary_path(
|
||||||
statuses.clone(),
|
statuses.clone(),
|
||||||
)
|
)
|
||||||
.await;
|
.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
|
statuses
|
||||||
.broadcast((language.clone(), LanguageServerBinaryStatus::Cached))
|
.broadcast((language.clone(), LanguageServerBinaryStatus::Cached))
|
||||||
.await?;
|
.await?;
|
||||||
return Ok(cached_path);
|
return Ok(cached);
|
||||||
} else {
|
} else {
|
||||||
statuses
|
statuses
|
||||||
.broadcast((
|
.broadcast((
|
||||||
|
@ -892,16 +903,16 @@ async fn get_server_binary_path(
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
path
|
binary
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch_latest_server_binary_path(
|
async fn fetch_latest_binary(
|
||||||
adapter: Arc<CachedLspAdapter>,
|
adapter: Arc<CachedLspAdapter>,
|
||||||
language: Arc<Language>,
|
language: Arc<Language>,
|
||||||
http_client: Arc<dyn HttpClient>,
|
http_client: Arc<dyn HttpClient>,
|
||||||
container_dir: &Path,
|
container_dir: &Path,
|
||||||
lsp_binary_statuses_tx: async_broadcast::Sender<(Arc<Language>, LanguageServerBinaryStatus)>,
|
lsp_binary_statuses_tx: async_broadcast::Sender<(Arc<Language>, LanguageServerBinaryStatus)>,
|
||||||
) -> Result<PathBuf> {
|
) -> Result<LanguageServerBinary> {
|
||||||
let container_dir: Arc<Path> = container_dir.into();
|
let container_dir: Arc<Path> = container_dir.into();
|
||||||
lsp_binary_statuses_tx
|
lsp_binary_statuses_tx
|
||||||
.broadcast((
|
.broadcast((
|
||||||
|
@ -915,13 +926,13 @@ async fn fetch_latest_server_binary_path(
|
||||||
lsp_binary_statuses_tx
|
lsp_binary_statuses_tx
|
||||||
.broadcast((language.clone(), LanguageServerBinaryStatus::Downloading))
|
.broadcast((language.clone(), LanguageServerBinaryStatus::Downloading))
|
||||||
.await?;
|
.await?;
|
||||||
let path = adapter
|
let binary = adapter
|
||||||
.fetch_server_binary(version_info, http_client, container_dir.to_path_buf())
|
.fetch_server_binary(version_info, http_client, container_dir.to_path_buf())
|
||||||
.await?;
|
.await?;
|
||||||
lsp_binary_statuses_tx
|
lsp_binary_statuses_tx
|
||||||
.broadcast((language.clone(), LanguageServerBinaryStatus::Downloaded))
|
.broadcast((language.clone(), LanguageServerBinaryStatus::Downloaded))
|
||||||
.await?;
|
.await?;
|
||||||
Ok(path)
|
Ok(binary)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Language {
|
impl Language {
|
||||||
|
@ -1454,11 +1465,11 @@ impl LspAdapter for Arc<FakeLspAdapter> {
|
||||||
_: Box<dyn 'static + Send + Any>,
|
_: Box<dyn 'static + Send + Any>,
|
||||||
_: Arc<dyn HttpClient>,
|
_: Arc<dyn HttpClient>,
|
||||||
_: PathBuf,
|
_: PathBuf,
|
||||||
) -> Result<PathBuf> {
|
) -> Result<LanguageServerBinary> {
|
||||||
unreachable!();
|
unreachable!();
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn cached_server_binary(&self, _: PathBuf) -> Option<PathBuf> {
|
async fn cached_server_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
|
||||||
unreachable!();
|
unreachable!();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1516,7 +1527,7 @@ mod tests {
|
||||||
|
|
||||||
#[gpui::test(iterations = 10)]
|
#[gpui::test(iterations = 10)]
|
||||||
async fn test_language_loading(cx: &mut TestAppContext) {
|
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());
|
languages.set_executor(cx.background());
|
||||||
let languages = Arc::new(languages);
|
let languages = Arc::new(languages);
|
||||||
languages.register(
|
languages.register(
|
||||||
|
|
|
@ -108,7 +108,7 @@ impl LanguageServer {
|
||||||
pub fn new<T: AsRef<std::ffi::OsStr>>(
|
pub fn new<T: AsRef<std::ffi::OsStr>>(
|
||||||
server_id: usize,
|
server_id: usize,
|
||||||
binary_path: &Path,
|
binary_path: &Path,
|
||||||
args: &[T],
|
arguments: &[T],
|
||||||
root_path: &Path,
|
root_path: &Path,
|
||||||
cx: AsyncAppContext,
|
cx: AsyncAppContext,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
|
@ -117,9 +117,10 @@ impl LanguageServer {
|
||||||
} else {
|
} else {
|
||||||
root_path.parent().unwrap_or_else(|| Path::new("/"))
|
root_path.parent().unwrap_or_else(|| Path::new("/"))
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut server = process::Command::new(binary_path)
|
let mut server = process::Command::new(binary_path)
|
||||||
.current_dir(working_dir)
|
.current_dir(working_dir)
|
||||||
.args(args)
|
.args(arguments)
|
||||||
.stdin(Stdio::piped())
|
.stdin(Stdio::piped())
|
||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
.stderr(Stdio::inherit())
|
.stderr(Stdio::inherit())
|
||||||
|
@ -128,7 +129,6 @@ impl LanguageServer {
|
||||||
|
|
||||||
let stdin = server.stdin.take().unwrap();
|
let stdin = server.stdin.take().unwrap();
|
||||||
let stout = server.stdout.take().unwrap();
|
let stout = server.stdout.take().unwrap();
|
||||||
|
|
||||||
let mut server = Self::new_internal(
|
let mut server = Self::new_internal(
|
||||||
server_id,
|
server_id,
|
||||||
stdin,
|
stdin,
|
||||||
|
@ -147,6 +147,7 @@ impl LanguageServer {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(name) = binary_path.file_name() {
|
if let Some(name) = binary_path.file_name() {
|
||||||
server.name = name.to_string_lossy().to_string();
|
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 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 CONFIG_DIR: PathBuf = HOME.join(".config").join("zed");
|
||||||
pub static ref LOGS_DIR: PathBuf = HOME.join("Library/Logs/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 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 DB_DIR: PathBuf = HOME.join("Library/Application Support/Zed/db");
|
||||||
pub static ref SETTINGS: PathBuf = CONFIG_DIR.join("settings.json");
|
pub static ref SETTINGS: PathBuf = CONFIG_DIR.join("settings.json");
|
||||||
|
|
|
@ -1,17 +1,21 @@
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
|
use client::http::HttpClient;
|
||||||
|
use gpui::executor::Background;
|
||||||
pub use language::*;
|
pub use language::*;
|
||||||
|
use node_runtime::NodeRuntime;
|
||||||
use rust_embed::RustEmbed;
|
use rust_embed::RustEmbed;
|
||||||
use std::{borrow::Cow, str, sync::Arc};
|
use std::{borrow::Cow, str, sync::Arc};
|
||||||
use theme::ThemeRegistry;
|
use theme::ThemeRegistry;
|
||||||
|
|
||||||
mod c;
|
mod c;
|
||||||
mod elixir;
|
mod elixir;
|
||||||
|
mod github;
|
||||||
mod go;
|
mod go;
|
||||||
mod html;
|
mod html;
|
||||||
mod installation;
|
|
||||||
mod json;
|
mod json;
|
||||||
mod language_plugin;
|
mod language_plugin;
|
||||||
mod lua;
|
mod lua;
|
||||||
|
mod node_runtime;
|
||||||
mod python;
|
mod python;
|
||||||
mod ruby;
|
mod ruby;
|
||||||
mod rust;
|
mod rust;
|
||||||
|
@ -32,7 +36,14 @@ mod yaml;
|
||||||
#[exclude = "*.rs"]
|
#[exclude = "*.rs"]
|
||||||
struct LanguageDir;
|
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 [
|
for (name, grammar, lsp_adapter) in [
|
||||||
(
|
(
|
||||||
"c",
|
"c",
|
||||||
|
@ -63,6 +74,7 @@ pub fn init(languages: Arc<LanguageRegistry>, themes: Arc<ThemeRegistry>) {
|
||||||
"json",
|
"json",
|
||||||
tree_sitter_json::language(),
|
tree_sitter_json::language(),
|
||||||
Some(Arc::new(json::JsonLspAdapter::new(
|
Some(Arc::new(json::JsonLspAdapter::new(
|
||||||
|
node_runtime.clone(),
|
||||||
languages.clone(),
|
languages.clone(),
|
||||||
themes.clone(),
|
themes.clone(),
|
||||||
))),
|
))),
|
||||||
|
@ -75,7 +87,9 @@ pub fn init(languages: Arc<LanguageRegistry>, themes: Arc<ThemeRegistry>) {
|
||||||
(
|
(
|
||||||
"python",
|
"python",
|
||||||
tree_sitter_python::language(),
|
tree_sitter_python::language(),
|
||||||
Some(Arc::new(python::PythonLspAdapter)),
|
Some(Arc::new(python::PythonLspAdapter::new(
|
||||||
|
node_runtime.clone(),
|
||||||
|
))),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"rust",
|
"rust",
|
||||||
|
@ -90,22 +104,28 @@ pub fn init(languages: Arc<LanguageRegistry>, themes: Arc<ThemeRegistry>) {
|
||||||
(
|
(
|
||||||
"tsx",
|
"tsx",
|
||||||
tree_sitter_typescript::language_tsx(),
|
tree_sitter_typescript::language_tsx(),
|
||||||
Some(Arc::new(typescript::TypeScriptLspAdapter)),
|
Some(Arc::new(typescript::TypeScriptLspAdapter::new(
|
||||||
|
node_runtime.clone(),
|
||||||
|
))),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"typescript",
|
"typescript",
|
||||||
tree_sitter_typescript::language_typescript(),
|
tree_sitter_typescript::language_typescript(),
|
||||||
Some(Arc::new(typescript::TypeScriptLspAdapter)),
|
Some(Arc::new(typescript::TypeScriptLspAdapter::new(
|
||||||
|
node_runtime.clone(),
|
||||||
|
))),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"javascript",
|
"javascript",
|
||||||
tree_sitter_typescript::language_tsx(),
|
tree_sitter_typescript::language_tsx(),
|
||||||
Some(Arc::new(typescript::TypeScriptLspAdapter)),
|
Some(Arc::new(typescript::TypeScriptLspAdapter::new(
|
||||||
|
node_runtime.clone(),
|
||||||
|
))),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"html",
|
"html",
|
||||||
tree_sitter_html::language(),
|
tree_sitter_html::language(),
|
||||||
Some(Arc::new(html::HtmlLspAdapter)),
|
Some(Arc::new(html::HtmlLspAdapter::new(node_runtime.clone()))),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"ruby",
|
"ruby",
|
||||||
|
@ -135,7 +155,7 @@ pub fn init(languages: Arc<LanguageRegistry>, themes: Arc<ThemeRegistry>) {
|
||||||
(
|
(
|
||||||
"yaml",
|
"yaml",
|
||||||
tree_sitter_yaml::language(),
|
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);
|
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 anyhow::{anyhow, Context, Result};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use client::http::HttpClient;
|
use client::http::HttpClient;
|
||||||
|
@ -39,7 +39,7 @@ impl super::LspAdapter for CLspAdapter {
|
||||||
version: Box<dyn 'static + Send + Any>,
|
version: Box<dyn 'static + Send + Any>,
|
||||||
http: Arc<dyn HttpClient>,
|
http: Arc<dyn HttpClient>,
|
||||||
container_dir: PathBuf,
|
container_dir: PathBuf,
|
||||||
) -> Result<PathBuf> {
|
) -> Result<LanguageServerBinary> {
|
||||||
let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
|
let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
|
||||||
let zip_path = container_dir.join(format!("clangd_{}.zip", version.name));
|
let zip_path = container_dir.join(format!("clangd_{}.zip", version.name));
|
||||||
let version_dir = container_dir.join(format!("clangd_{}", 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 {
|
(|| async move {
|
||||||
let mut last_clangd_dir = None;
|
let mut last_clangd_dir = None;
|
||||||
let mut entries = fs::read_dir(&container_dir).await?;
|
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_dir = last_clangd_dir.ok_or_else(|| anyhow!("no cached binary"))?;
|
||||||
let clangd_bin = clangd_dir.join("bin/clangd");
|
let clangd_bin = clangd_dir.join("bin/clangd");
|
||||||
if clangd_bin.exists() {
|
if clangd_bin.exists() {
|
||||||
Ok(clangd_bin)
|
Ok(LanguageServerBinary {
|
||||||
|
path: clangd_bin,
|
||||||
|
arguments: vec![],
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!(
|
Err(anyhow!(
|
||||||
"missing clangd binary in directory {:?}",
|
"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 anyhow::{anyhow, Context, Result};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use client::http::HttpClient;
|
use client::http::HttpClient;
|
||||||
|
@ -40,7 +40,7 @@ impl LspAdapter for ElixirLspAdapter {
|
||||||
version: Box<dyn 'static + Send + Any>,
|
version: Box<dyn 'static + Send + Any>,
|
||||||
http: Arc<dyn HttpClient>,
|
http: Arc<dyn HttpClient>,
|
||||||
container_dir: PathBuf,
|
container_dir: PathBuf,
|
||||||
) -> Result<PathBuf> {
|
) -> Result<LanguageServerBinary> {
|
||||||
let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
|
let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
|
||||||
let zip_path = container_dir.join(format!("elixir-ls_{}.zip", version.name));
|
let zip_path = container_dir.join(format!("elixir-ls_{}.zip", version.name));
|
||||||
let version_dir = container_dir.join(format!("elixir-ls_{}", 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 {
|
(|| async move {
|
||||||
let mut last = None;
|
let mut last = None;
|
||||||
let mut entries = fs::read_dir(&container_dir).await?;
|
let mut entries = fs::read_dir(&container_dir).await?;
|
||||||
while let Some(entry) = entries.next().await {
|
while let Some(entry) = entries.next().await {
|
||||||
last = Some(entry?.path());
|
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
|
.await
|
||||||
.log_err()
|
.log_err()
|
||||||
|
|
45
crates/zed/src/languages/github.rs
Normal file
45
crates/zed/src/languages/github.rs
Normal file
|
@ -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 anyhow::{anyhow, Result};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use client::http::HttpClient;
|
use client::http::HttpClient;
|
||||||
|
@ -7,9 +7,13 @@ pub use language::*;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use smol::{fs, process};
|
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;
|
use util::ResultExt;
|
||||||
|
|
||||||
|
fn server_binary_arguments() -> Vec<OsString> {
|
||||||
|
vec!["-mode=stdio".into()]
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct GoLspAdapter;
|
pub struct GoLspAdapter;
|
||||||
|
|
||||||
|
@ -23,10 +27,6 @@ impl super::LspAdapter for GoLspAdapter {
|
||||||
LanguageServerName("gopls".into())
|
LanguageServerName("gopls".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn server_args(&self) -> Vec<String> {
|
|
||||||
vec!["-mode=stdio".into()]
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn fetch_latest_server_version(
|
async fn fetch_latest_server_version(
|
||||||
&self,
|
&self,
|
||||||
http: Arc<dyn HttpClient>,
|
http: Arc<dyn HttpClient>,
|
||||||
|
@ -47,7 +47,7 @@ impl super::LspAdapter for GoLspAdapter {
|
||||||
version: Box<dyn 'static + Send + Any>,
|
version: Box<dyn 'static + Send + Any>,
|
||||||
_: Arc<dyn HttpClient>,
|
_: Arc<dyn HttpClient>,
|
||||||
container_dir: PathBuf,
|
container_dir: PathBuf,
|
||||||
) -> Result<PathBuf> {
|
) -> Result<LanguageServerBinary> {
|
||||||
let version = version.downcast::<Option<String>>().unwrap();
|
let version = version.downcast::<Option<String>>().unwrap();
|
||||||
let this = *self;
|
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 {
|
} 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}"));
|
let binary_path = container_dir.join(&format!("gopls_{version}"));
|
||||||
fs::rename(&installed_binary_path, &binary_path).await?;
|
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 {
|
(|| async move {
|
||||||
let mut last_binary_path = None;
|
let mut last_binary_path = None;
|
||||||
let mut entries = fs::read_dir(&container_dir).await?;
|
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 {
|
if let Some(path) = last_binary_path {
|
||||||
Ok(path)
|
Ok(LanguageServerBinary {
|
||||||
|
path,
|
||||||
|
arguments: server_binary_arguments(),
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!("no cached binary"))
|
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 anyhow::{anyhow, Context, Result};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use client::http::HttpClient;
|
use client::http::HttpClient;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use language::{LanguageServerName, LspAdapter};
|
use language::{LanguageServerBinary, LanguageServerName, LspAdapter};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use smol::fs;
|
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;
|
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 {
|
impl HtmlLspAdapter {
|
||||||
const BIN_PATH: &'static str =
|
const SERVER_PATH: &'static str =
|
||||||
"node_modules/vscode-langservers-extracted/bin/vscode-html-language-server";
|
"node_modules/vscode-langservers-extracted/bin/vscode-html-language-server";
|
||||||
|
|
||||||
|
pub fn new(node: Arc<NodeRuntime>) -> Self {
|
||||||
|
HtmlLspAdapter { node }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
|
@ -22,15 +37,15 @@ impl LspAdapter for HtmlLspAdapter {
|
||||||
LanguageServerName("vscode-html-language-server".into())
|
LanguageServerName("vscode-html-language-server".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn server_args(&self) -> Vec<String> {
|
|
||||||
vec!["--stdio".into()]
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn fetch_latest_server_version(
|
async fn fetch_latest_server_version(
|
||||||
&self,
|
&self,
|
||||||
_: Arc<dyn HttpClient>,
|
_: Arc<dyn HttpClient>,
|
||||||
) -> Result<Box<dyn 'static + Any + Send>> {
|
) -> 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(
|
async fn fetch_server_binary(
|
||||||
|
@ -38,16 +53,17 @@ impl LspAdapter for HtmlLspAdapter {
|
||||||
version: Box<dyn 'static + Send + Any>,
|
version: Box<dyn 'static + Send + Any>,
|
||||||
_: Arc<dyn HttpClient>,
|
_: Arc<dyn HttpClient>,
|
||||||
container_dir: PathBuf,
|
container_dir: PathBuf,
|
||||||
) -> Result<PathBuf> {
|
) -> Result<LanguageServerBinary> {
|
||||||
let version = version.downcast::<String>().unwrap();
|
let version = version.downcast::<String>().unwrap();
|
||||||
let version_dir = container_dir.join(version.as_str());
|
let version_dir = container_dir.join(version.as_str());
|
||||||
fs::create_dir_all(&version_dir)
|
fs::create_dir_all(&version_dir)
|
||||||
.await
|
.await
|
||||||
.context("failed to create version directory")?;
|
.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() {
|
if fs::metadata(&server_path).await.is_err() {
|
||||||
npm_install_packages(
|
self.node
|
||||||
|
.npm_install_packages(
|
||||||
[("vscode-langservers-extracted", version.as_str())],
|
[("vscode-langservers-extracted", version.as_str())],
|
||||||
&version_dir,
|
&version_dir,
|
||||||
)
|
)
|
||||||
|
@ -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 {
|
(|| async move {
|
||||||
let mut last_version_dir = None;
|
let mut last_version_dir = None;
|
||||||
let mut entries = fs::read_dir(&container_dir).await?;
|
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 last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
|
||||||
let bin_path = last_version_dir.join(Self::BIN_PATH);
|
let server_path = last_version_dir.join(Self::SERVER_PATH);
|
||||||
if bin_path.exists() {
|
if server_path.exists() {
|
||||||
Ok(bin_path)
|
Ok(LanguageServerBinary {
|
||||||
|
path: self.node.binary_path().await?,
|
||||||
|
arguments: server_binary_arguments(&server_path),
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!(
|
Err(anyhow!(
|
||||||
"missing executable in directory {:?}",
|
"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 super::node_runtime::NodeRuntime;
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use async_compression::futures::bufread::GzipDecoder;
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use client::http::HttpClient;
|
use client::http::HttpClient;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use futures::{future::BoxFuture, io::BufReader, FutureExt, StreamExt};
|
use futures::{future::BoxFuture, FutureExt, StreamExt};
|
||||||
use gpui::MutableAppContext;
|
use gpui::MutableAppContext;
|
||||||
use language::{LanguageRegistry, LanguageServerName, LspAdapter};
|
use language::{LanguageRegistry, LanguageServerBinary, LanguageServerName, LspAdapter};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use settings::{keymap_file_json_schema, settings_file_json_schema};
|
use settings::{keymap_file_json_schema, settings_file_json_schema};
|
||||||
use smol::fs::{self, File};
|
use smol::fs;
|
||||||
use std::{
|
use std::{
|
||||||
any::Any,
|
any::Any,
|
||||||
env::consts,
|
ffi::OsString,
|
||||||
future,
|
future,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
|
@ -20,14 +19,30 @@ use std::{
|
||||||
use theme::ThemeRegistry;
|
use theme::ThemeRegistry;
|
||||||
use util::{paths, ResultExt, StaffMode};
|
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 {
|
pub struct JsonLspAdapter {
|
||||||
|
node: Arc<NodeRuntime>,
|
||||||
languages: Arc<LanguageRegistry>,
|
languages: Arc<LanguageRegistry>,
|
||||||
themes: Arc<ThemeRegistry>,
|
themes: Arc<ThemeRegistry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JsonLspAdapter {
|
impl JsonLspAdapter {
|
||||||
pub fn new(languages: Arc<LanguageRegistry>, themes: Arc<ThemeRegistry>) -> Self {
|
pub fn new(
|
||||||
Self { languages, themes }
|
node: Arc<NodeRuntime>,
|
||||||
|
languages: Arc<LanguageRegistry>,
|
||||||
|
themes: Arc<ThemeRegistry>,
|
||||||
|
) -> Self {
|
||||||
|
JsonLspAdapter {
|
||||||
|
node,
|
||||||
|
languages,
|
||||||
|
themes,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,52 +52,35 @@ impl LspAdapter for JsonLspAdapter {
|
||||||
LanguageServerName("json-language-server".into())
|
LanguageServerName("json-language-server".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn server_args(&self) -> Vec<String> {
|
|
||||||
vec!["--stdio".into()]
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn fetch_latest_server_version(
|
async fn fetch_latest_server_version(
|
||||||
&self,
|
&self,
|
||||||
http: Arc<dyn HttpClient>,
|
_: Arc<dyn HttpClient>,
|
||||||
) -> Result<Box<dyn 'static + Send + Any>> {
|
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||||
let release = latest_github_release("zed-industries/json-language-server", http).await?;
|
Ok(Box::new(
|
||||||
let asset_name = format!("json-language-server-darwin-{}.gz", consts::ARCH);
|
self.node
|
||||||
let asset = release
|
.npm_package_latest_version("vscode-json-languageserver")
|
||||||
.assets
|
.await?,
|
||||||
.iter()
|
) as Box<_>)
|
||||||
.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<_>)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch_server_binary(
|
async fn fetch_server_binary(
|
||||||
&self,
|
&self,
|
||||||
version: Box<dyn 'static + Send + Any>,
|
version: Box<dyn 'static + Send + Any>,
|
||||||
http: Arc<dyn HttpClient>,
|
_: Arc<dyn HttpClient>,
|
||||||
container_dir: PathBuf,
|
container_dir: PathBuf,
|
||||||
) -> Result<PathBuf> {
|
) -> Result<LanguageServerBinary> {
|
||||||
let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
|
let version = version.downcast::<String>().unwrap();
|
||||||
let destination_path = container_dir.join(format!(
|
let version_dir = container_dir.join(version.as_str());
|
||||||
"json-language-server-{}-{}",
|
fs::create_dir_all(&version_dir)
|
||||||
version.name,
|
|
||||||
consts::ARCH
|
|
||||||
));
|
|
||||||
|
|
||||||
if fs::metadata(&destination_path).await.is_err() {
|
|
||||||
let mut response = http
|
|
||||||
.get(&version.url, Default::default(), true)
|
|
||||||
.await
|
.await
|
||||||
.map_err(|err| anyhow!("error downloading release: {}", err))?;
|
.context("failed to create version directory")?;
|
||||||
let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
|
let server_path = version_dir.join(SERVER_PATH);
|
||||||
let mut file = File::create(&destination_path).await?;
|
|
||||||
futures::io::copy(decompressed_bytes, &mut file).await?;
|
if fs::metadata(&server_path).await.is_err() {
|
||||||
fs::set_permissions(
|
self.node
|
||||||
&destination_path,
|
.npm_install_packages(
|
||||||
<fs::Permissions as fs::unix::PermissionsExt>::from_mode(0o755),
|
[("vscode-json-languageserver", version.as_str())],
|
||||||
|
&version_dir,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
@ -90,25 +88,44 @@ impl LspAdapter for JsonLspAdapter {
|
||||||
while let Some(entry) = entries.next().await {
|
while let Some(entry) = entries.next().await {
|
||||||
if let Some(entry) = entry.log_err() {
|
if let Some(entry) = entry.log_err() {
|
||||||
let entry_path = entry.path();
|
let entry_path = entry.path();
|
||||||
if entry_path.as_path() != destination_path {
|
if entry_path.as_path() != version_dir {
|
||||||
fs::remove_file(&entry_path).await.log_err();
|
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 {
|
(|| async move {
|
||||||
let mut last = None;
|
let mut last_version_dir = None;
|
||||||
let mut entries = fs::read_dir(&container_dir).await?;
|
let mut entries = fs::read_dir(&container_dir).await?;
|
||||||
while let Some(entry) = entries.next().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
|
.await
|
||||||
.log_err()
|
.log_err()
|
||||||
|
|
|
@ -4,7 +4,7 @@ use client::http::HttpClient;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use futures::lock::Mutex;
|
use futures::lock::Mutex;
|
||||||
use gpui::executor::Background;
|
use gpui::executor::Background;
|
||||||
use language::{LanguageServerName, LspAdapter};
|
use language::{LanguageServerBinary, LanguageServerName, LspAdapter};
|
||||||
use plugin_runtime::{Plugin, PluginBinary, PluginBuilder, WasiFn};
|
use plugin_runtime::{Plugin, PluginBinary, PluginBuilder, WasiFn};
|
||||||
use std::{any::Any, path::PathBuf, sync::Arc};
|
use std::{any::Any, path::PathBuf, sync::Arc};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
@ -32,10 +32,9 @@ pub async fn new_json(executor: Arc<Background>) -> Result<PluginLspAdapter> {
|
||||||
|
|
||||||
pub struct PluginLspAdapter {
|
pub struct PluginLspAdapter {
|
||||||
name: WasiFn<(), String>,
|
name: WasiFn<(), String>,
|
||||||
server_args: WasiFn<(), Vec<String>>,
|
|
||||||
fetch_latest_server_version: WasiFn<(), Option<String>>,
|
fetch_latest_server_version: WasiFn<(), Option<String>>,
|
||||||
fetch_server_binary: WasiFn<(PathBuf, String), Result<PathBuf, String>>,
|
fetch_server_binary: WasiFn<(PathBuf, String), Result<LanguageServerBinary, String>>,
|
||||||
cached_server_binary: WasiFn<PathBuf, Option<PathBuf>>,
|
cached_server_binary: WasiFn<PathBuf, Option<LanguageServerBinary>>,
|
||||||
initialization_options: WasiFn<(), String>,
|
initialization_options: WasiFn<(), String>,
|
||||||
language_ids: WasiFn<(), Vec<(String, String)>>,
|
language_ids: WasiFn<(), Vec<(String, String)>>,
|
||||||
executor: Arc<Background>,
|
executor: Arc<Background>,
|
||||||
|
@ -47,7 +46,6 @@ impl PluginLspAdapter {
|
||||||
pub async fn new(mut plugin: Plugin, executor: Arc<Background>) -> Result<Self> {
|
pub async fn new(mut plugin: Plugin, executor: Arc<Background>) -> Result<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
name: plugin.function("name")?,
|
name: plugin.function("name")?,
|
||||||
server_args: plugin.function("server_args")?,
|
|
||||||
fetch_latest_server_version: plugin.function("fetch_latest_server_version")?,
|
fetch_latest_server_version: plugin.function("fetch_latest_server_version")?,
|
||||||
fetch_server_binary: plugin.function("fetch_server_binary")?,
|
fetch_server_binary: plugin.function("fetch_server_binary")?,
|
||||||
cached_server_binary: plugin.function("cached_server_binary")?,
|
cached_server_binary: plugin.function("cached_server_binary")?,
|
||||||
|
@ -72,15 +70,6 @@ impl LspAdapter for PluginLspAdapter {
|
||||||
LanguageServerName(name.into())
|
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(
|
async fn fetch_latest_server_version(
|
||||||
&self,
|
&self,
|
||||||
_: Arc<dyn HttpClient>,
|
_: Arc<dyn HttpClient>,
|
||||||
|
@ -105,7 +94,7 @@ impl LspAdapter for PluginLspAdapter {
|
||||||
version: Box<dyn 'static + Send + Any>,
|
version: Box<dyn 'static + Send + Any>,
|
||||||
_: Arc<dyn HttpClient>,
|
_: Arc<dyn HttpClient>,
|
||||||
container_dir: PathBuf,
|
container_dir: PathBuf,
|
||||||
) -> Result<PathBuf> {
|
) -> Result<LanguageServerBinary> {
|
||||||
let version = *version.downcast::<String>().unwrap();
|
let version = *version.downcast::<String>().unwrap();
|
||||||
let runtime = self.runtime.clone();
|
let runtime = self.runtime.clone();
|
||||||
let function = self.fetch_server_binary;
|
let function = self.fetch_server_binary;
|
||||||
|
@ -113,7 +102,7 @@ impl LspAdapter for PluginLspAdapter {
|
||||||
.spawn(async move {
|
.spawn(async move {
|
||||||
let mut runtime = runtime.lock().await;
|
let mut runtime = runtime.lock().await;
|
||||||
let handle = runtime.attach_path(&container_dir)?;
|
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.call(&function, (container_dir, version)).await?;
|
||||||
runtime.remove_resource(handle)?;
|
runtime.remove_resource(handle)?;
|
||||||
result.map_err(|e| anyhow!("{}", e))
|
result.map_err(|e| anyhow!("{}", e))
|
||||||
|
@ -121,7 +110,7 @@ impl LspAdapter for PluginLspAdapter {
|
||||||
.await
|
.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 runtime = self.runtime.clone();
|
||||||
let function = self.cached_server_binary;
|
let function = self.cached_server_binary;
|
||||||
|
|
||||||
|
@ -129,7 +118,8 @@ impl LspAdapter for PluginLspAdapter {
|
||||||
.spawn(async move {
|
.spawn(async move {
|
||||||
let mut runtime = runtime.lock().await;
|
let mut runtime = runtime.lock().await;
|
||||||
let handle = runtime.attach_path(&container_dir).ok()?;
|
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()?;
|
runtime.remove_resource(handle).ok()?;
|
||||||
result
|
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 anyhow::{anyhow, bail, Result};
|
||||||
use async_compression::futures::bufread::GzipDecoder;
|
use async_compression::futures::bufread::GzipDecoder;
|
||||||
|
@ -6,28 +6,28 @@ use async_tar::Archive;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use client::http::HttpClient;
|
use client::http::HttpClient;
|
||||||
use futures::{io::BufReader, StreamExt};
|
use futures::{io::BufReader, StreamExt};
|
||||||
use language::LanguageServerName;
|
use language::{LanguageServerBinary, LanguageServerName};
|
||||||
use smol::fs;
|
use smol::fs;
|
||||||
use util::{async_iife, ResultExt};
|
use util::{async_iife, ResultExt};
|
||||||
|
|
||||||
use super::installation::{latest_github_release, GitHubLspBinaryVersion};
|
use super::github::{latest_github_release, GitHubLspBinaryVersion};
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct LuaLspAdapter;
|
pub struct LuaLspAdapter;
|
||||||
|
|
||||||
|
fn server_binary_arguments() -> Vec<OsString> {
|
||||||
|
vec![
|
||||||
|
"--logpath=~/lua-language-server.log".into(),
|
||||||
|
"--loglevel=trace".into(),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl super::LspAdapter for LuaLspAdapter {
|
impl super::LspAdapter for LuaLspAdapter {
|
||||||
async fn name(&self) -> LanguageServerName {
|
async fn name(&self) -> LanguageServerName {
|
||||||
LanguageServerName("lua-language-server".into())
|
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(
|
async fn fetch_latest_server_version(
|
||||||
&self,
|
&self,
|
||||||
http: Arc<dyn HttpClient>,
|
http: Arc<dyn HttpClient>,
|
||||||
|
@ -57,7 +57,7 @@ impl super::LspAdapter for LuaLspAdapter {
|
||||||
version: Box<dyn 'static + Send + Any>,
|
version: Box<dyn 'static + Send + Any>,
|
||||||
http: Arc<dyn HttpClient>,
|
http: Arc<dyn HttpClient>,
|
||||||
container_dir: PathBuf,
|
container_dir: PathBuf,
|
||||||
) -> Result<PathBuf> {
|
) -> Result<LanguageServerBinary> {
|
||||||
let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
|
let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
|
||||||
|
|
||||||
let binary_path = container_dir.join("bin/lua-language-server");
|
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),
|
<fs::Permissions as fs::unix::PermissionsExt>::from_mode(0o755),
|
||||||
)
|
)
|
||||||
.await?;
|
.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!({
|
async_iife!({
|
||||||
let mut last_binary_path = None;
|
let mut last_binary_path = None;
|
||||||
let mut entries = fs::read_dir(&container_dir).await?;
|
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 {
|
if let Some(path) = last_binary_path {
|
||||||
Ok(path)
|
Ok(LanguageServerBinary {
|
||||||
|
path,
|
||||||
|
arguments: server_binary_arguments(),
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!("no cached binary"))
|
Err(anyhow!("no cached binary"))
|
||||||
}
|
}
|
||||||
|
|
166
crates/zed/src/languages/node_runtime.rs
Normal file
166
crates/zed/src/languages/node_runtime.rs
Normal file
|
@ -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 anyhow::{anyhow, Context, Result};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use client::http::HttpClient;
|
use client::http::HttpClient;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use language::{LanguageServerName, LspAdapter};
|
use language::{LanguageServerBinary, LanguageServerName, LspAdapter};
|
||||||
use smol::fs;
|
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;
|
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 {
|
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]
|
#[async_trait]
|
||||||
|
@ -20,15 +35,11 @@ impl LspAdapter for PythonLspAdapter {
|
||||||
LanguageServerName("pyright".into())
|
LanguageServerName("pyright".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn server_args(&self) -> Vec<String> {
|
|
||||||
vec!["--stdio".into()]
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn fetch_latest_server_version(
|
async fn fetch_latest_server_version(
|
||||||
&self,
|
&self,
|
||||||
_: Arc<dyn HttpClient>,
|
_: Arc<dyn HttpClient>,
|
||||||
) -> Result<Box<dyn 'static + Any + Send>> {
|
) -> 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(
|
async fn fetch_server_binary(
|
||||||
|
@ -36,16 +47,18 @@ impl LspAdapter for PythonLspAdapter {
|
||||||
version: Box<dyn 'static + Send + Any>,
|
version: Box<dyn 'static + Send + Any>,
|
||||||
_: Arc<dyn HttpClient>,
|
_: Arc<dyn HttpClient>,
|
||||||
container_dir: PathBuf,
|
container_dir: PathBuf,
|
||||||
) -> Result<PathBuf> {
|
) -> Result<LanguageServerBinary> {
|
||||||
let version = version.downcast::<String>().unwrap();
|
let version = version.downcast::<String>().unwrap();
|
||||||
let version_dir = container_dir.join(version.as_str());
|
let version_dir = container_dir.join(version.as_str());
|
||||||
fs::create_dir_all(&version_dir)
|
fs::create_dir_all(&version_dir)
|
||||||
.await
|
.await
|
||||||
.context("failed to create version directory")?;
|
.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() {
|
if fs::metadata(&server_path).await.is_err() {
|
||||||
npm_install_packages([("pyright", version.as_str())], &version_dir).await?;
|
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() {
|
if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() {
|
||||||
while let Some(entry) = entries.next().await {
|
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 {
|
(|| async move {
|
||||||
let mut last_version_dir = None;
|
let mut last_version_dir = None;
|
||||||
let mut entries = fs::read_dir(&container_dir).await?;
|
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 last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
|
||||||
let bin_path = last_version_dir.join(Self::BIN_PATH);
|
let server_path = last_version_dir.join(Self::SERVER_PATH);
|
||||||
if bin_path.exists() {
|
if server_path.exists() {
|
||||||
Ok(bin_path)
|
Ok(LanguageServerBinary {
|
||||||
|
path: self.node.binary_path().await?,
|
||||||
|
arguments: server_binary_arguments(&server_path),
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!(
|
Err(anyhow!(
|
||||||
"missing executable in directory {:?}",
|
"missing executable in directory {:?}",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use client::http::HttpClient;
|
use client::http::HttpClient;
|
||||||
use language::{LanguageServerName, LspAdapter};
|
use language::{LanguageServerBinary, LanguageServerName, LspAdapter};
|
||||||
use std::{any::Any, path::PathBuf, sync::Arc};
|
use std::{any::Any, path::PathBuf, sync::Arc};
|
||||||
|
|
||||||
pub struct RubyLanguageServer;
|
pub struct RubyLanguageServer;
|
||||||
|
@ -12,10 +12,6 @@ impl LspAdapter for RubyLanguageServer {
|
||||||
LanguageServerName("solargraph".into())
|
LanguageServerName("solargraph".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn server_args(&self) -> Vec<String> {
|
|
||||||
vec!["stdio".into()]
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn fetch_latest_server_version(
|
async fn fetch_latest_server_version(
|
||||||
&self,
|
&self,
|
||||||
_: Arc<dyn HttpClient>,
|
_: Arc<dyn HttpClient>,
|
||||||
|
@ -28,12 +24,15 @@ impl LspAdapter for RubyLanguageServer {
|
||||||
_version: Box<dyn 'static + Send + Any>,
|
_version: Box<dyn 'static + Send + Any>,
|
||||||
_: Arc<dyn HttpClient>,
|
_: Arc<dyn HttpClient>,
|
||||||
_container_dir: PathBuf,
|
_container_dir: PathBuf,
|
||||||
) -> Result<PathBuf> {
|
) -> Result<LanguageServerBinary> {
|
||||||
Err(anyhow!("solargraph must be installed manually"))
|
Err(anyhow!("solargraph must be installed manually"))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn cached_server_binary(&self, _container_dir: PathBuf) -> Option<PathBuf> {
|
async fn cached_server_binary(&self, _container_dir: PathBuf) -> Option<LanguageServerBinary> {
|
||||||
Some("solargraph".into())
|
Some(LanguageServerBinary {
|
||||||
|
path: "solargraph".into(),
|
||||||
|
arguments: vec!["stdio".into()],
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn label_for_completion(
|
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 anyhow::{anyhow, Result};
|
||||||
use async_compression::futures::bufread::GzipDecoder;
|
use async_compression::futures::bufread::GzipDecoder;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
@ -42,7 +42,7 @@ impl LspAdapter for RustLspAdapter {
|
||||||
version: Box<dyn 'static + Send + Any>,
|
version: Box<dyn 'static + Send + Any>,
|
||||||
http: Arc<dyn HttpClient>,
|
http: Arc<dyn HttpClient>,
|
||||||
container_dir: PathBuf,
|
container_dir: PathBuf,
|
||||||
) -> Result<PathBuf> {
|
) -> Result<LanguageServerBinary> {
|
||||||
let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
|
let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
|
||||||
let destination_path = container_dir.join(format!("rust-analyzer-{}", version.name));
|
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 {
|
(|| async move {
|
||||||
let mut last = None;
|
let mut last = None;
|
||||||
let mut entries = fs::read_dir(&container_dir).await?;
|
let mut entries = fs::read_dir(&container_dir).await?;
|
||||||
while let Some(entry) = entries.next().await {
|
while let Some(entry) = entries.next().await {
|
||||||
last = Some(entry?.path());
|
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
|
.await
|
||||||
.log_err()
|
.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 anyhow::{anyhow, Context, Result};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use client::http::HttpClient;
|
use client::http::HttpClient;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use language::{LanguageServerName, LspAdapter};
|
use language::{LanguageServerBinary, LanguageServerName, LspAdapter};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use smol::fs;
|
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;
|
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 {
|
impl TypeScriptLspAdapter {
|
||||||
const OLD_BIN_PATH: &'static str = "node_modules/typescript-language-server/lib/cli.js";
|
const OLD_SERVER_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 NEW_SERVER_PATH: &'static str = "node_modules/typescript-language-server/lib/cli.mjs";
|
||||||
|
|
||||||
|
pub fn new(node: Arc<NodeRuntime>) -> Self {
|
||||||
|
TypeScriptLspAdapter { node }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Versions {
|
struct Versions {
|
||||||
|
@ -27,20 +47,16 @@ impl LspAdapter for TypeScriptLspAdapter {
|
||||||
LanguageServerName("typescript-language-server".into())
|
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(
|
async fn fetch_latest_server_version(
|
||||||
&self,
|
&self,
|
||||||
_: Arc<dyn HttpClient>,
|
_: Arc<dyn HttpClient>,
|
||||||
) -> Result<Box<dyn 'static + Send + Any>> {
|
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||||
Ok(Box::new(Versions {
|
Ok(Box::new(Versions {
|
||||||
typescript_version: npm_package_latest_version("typescript").await?,
|
typescript_version: self.node.npm_package_latest_version("typescript").await?,
|
||||||
server_version: npm_package_latest_version("typescript-language-server").await?,
|
server_version: self
|
||||||
|
.node
|
||||||
|
.npm_package_latest_version("typescript-language-server")
|
||||||
|
.await?,
|
||||||
}) as Box<_>)
|
}) as Box<_>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,7 +65,7 @@ impl LspAdapter for TypeScriptLspAdapter {
|
||||||
versions: Box<dyn 'static + Send + Any>,
|
versions: Box<dyn 'static + Send + Any>,
|
||||||
_: Arc<dyn HttpClient>,
|
_: Arc<dyn HttpClient>,
|
||||||
container_dir: PathBuf,
|
container_dir: PathBuf,
|
||||||
) -> Result<PathBuf> {
|
) -> Result<LanguageServerBinary> {
|
||||||
let versions = versions.downcast::<Versions>().unwrap();
|
let versions = versions.downcast::<Versions>().unwrap();
|
||||||
let version_dir = container_dir.join(&format!(
|
let version_dir = container_dir.join(&format!(
|
||||||
"typescript-{}:server-{}",
|
"typescript-{}:server-{}",
|
||||||
|
@ -58,10 +74,11 @@ impl LspAdapter for TypeScriptLspAdapter {
|
||||||
fs::create_dir_all(&version_dir)
|
fs::create_dir_all(&version_dir)
|
||||||
.await
|
.await
|
||||||
.context("failed to create version directory")?;
|
.context("failed to create version directory")?;
|
||||||
let binary_path = version_dir.join(Self::NEW_BIN_PATH);
|
let server_path = version_dir.join(Self::NEW_SERVER_PATH);
|
||||||
|
|
||||||
if fs::metadata(&binary_path).await.is_err() {
|
if fs::metadata(&server_path).await.is_err() {
|
||||||
npm_install_packages(
|
self.node
|
||||||
|
.npm_install_packages(
|
||||||
[
|
[
|
||||||
("typescript", versions.typescript_version.as_str()),
|
("typescript", versions.typescript_version.as_str()),
|
||||||
(
|
(
|
||||||
|
@ -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 {
|
(|| async move {
|
||||||
let mut last_version_dir = None;
|
let mut last_version_dir = None;
|
||||||
let mut entries = fs::read_dir(&container_dir).await?;
|
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 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 old_server_path = last_version_dir.join(Self::OLD_SERVER_PATH);
|
||||||
let new_bin_path = last_version_dir.join(Self::NEW_BIN_PATH);
|
let new_server_path = last_version_dir.join(Self::NEW_SERVER_PATH);
|
||||||
if new_bin_path.exists() {
|
if new_server_path.exists() {
|
||||||
Ok(new_bin_path)
|
Ok(LanguageServerBinary {
|
||||||
} else if old_bin_path.exists() {
|
path: self.node.binary_path().await?,
|
||||||
Ok(old_bin_path)
|
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 {
|
} else {
|
||||||
Err(anyhow!(
|
Err(anyhow!(
|
||||||
"missing executable in directory {:?}",
|
"missing executable in directory {:?}",
|
||||||
|
|
|
@ -1,21 +1,36 @@
|
||||||
|
use super::node_runtime::NodeRuntime;
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use client::http::HttpClient;
|
use client::http::HttpClient;
|
||||||
use futures::{future::BoxFuture, FutureExt, StreamExt};
|
use futures::{future::BoxFuture, FutureExt, StreamExt};
|
||||||
use gpui::MutableAppContext;
|
use gpui::MutableAppContext;
|
||||||
use language::{LanguageServerName, LspAdapter};
|
use language::{LanguageServerBinary, LanguageServerName, LspAdapter};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use smol::fs;
|
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 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 {
|
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]
|
#[async_trait]
|
||||||
|
@ -24,15 +39,15 @@ impl LspAdapter for YamlLspAdapter {
|
||||||
LanguageServerName("yaml-language-server".into())
|
LanguageServerName("yaml-language-server".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn server_args(&self) -> Vec<String> {
|
|
||||||
vec!["--stdio".into()]
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn fetch_latest_server_version(
|
async fn fetch_latest_server_version(
|
||||||
&self,
|
&self,
|
||||||
_: Arc<dyn HttpClient>,
|
_: Arc<dyn HttpClient>,
|
||||||
) -> Result<Box<dyn 'static + Any + Send>> {
|
) -> 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(
|
async fn fetch_server_binary(
|
||||||
|
@ -40,16 +55,17 @@ impl LspAdapter for YamlLspAdapter {
|
||||||
version: Box<dyn 'static + Send + Any>,
|
version: Box<dyn 'static + Send + Any>,
|
||||||
_: Arc<dyn HttpClient>,
|
_: Arc<dyn HttpClient>,
|
||||||
container_dir: PathBuf,
|
container_dir: PathBuf,
|
||||||
) -> Result<PathBuf> {
|
) -> Result<LanguageServerBinary> {
|
||||||
let version = version.downcast::<String>().unwrap();
|
let version = version.downcast::<String>().unwrap();
|
||||||
let version_dir = container_dir.join(version.as_str());
|
let version_dir = container_dir.join(version.as_str());
|
||||||
fs::create_dir_all(&version_dir)
|
fs::create_dir_all(&version_dir)
|
||||||
.await
|
.await
|
||||||
.context("failed to create version directory")?;
|
.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() {
|
if fs::metadata(&server_path).await.is_err() {
|
||||||
npm_install_packages([("yaml-language-server", version.as_str())], &version_dir)
|
self.node
|
||||||
|
.npm_install_packages([("yaml-language-server", version.as_str())], &version_dir)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() {
|
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 {
|
(|| async move {
|
||||||
let mut last_version_dir = None;
|
let mut last_version_dir = None;
|
||||||
let mut entries = fs::read_dir(&container_dir).await?;
|
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 last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
|
||||||
let bin_path = last_version_dir.join(Self::BIN_PATH);
|
let server_path = last_version_dir.join(Self::SERVER_PATH);
|
||||||
if bin_path.exists() {
|
if server_path.exists() {
|
||||||
Ok(bin_path)
|
Ok(LanguageServerBinary {
|
||||||
|
path: self.node.binary_path().await?,
|
||||||
|
arguments: server_binary_arguments(&server_path),
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!(
|
Err(anyhow!(
|
||||||
"missing executable in directory {:?}",
|
"missing executable in directory {:?}",
|
||||||
|
|
|
@ -139,7 +139,12 @@ fn main() {
|
||||||
languages.set_executor(cx.background().clone());
|
languages.set_executor(cx.background().clone());
|
||||||
languages.set_language_server_download_dir(paths::LANGUAGES_DIR.clone());
|
languages.set_language_server_download_dir(paths::LANGUAGES_DIR.clone());
|
||||||
let languages = Arc::new(languages);
|
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));
|
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx));
|
||||||
|
|
||||||
cx.set_global(client.clone());
|
cx.set_global(client.clone());
|
||||||
|
|
|
@ -652,9 +652,10 @@ fn open_bundled_file(
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use assets::Assets;
|
use assets::Assets;
|
||||||
|
use client::test::FakeHttpClient;
|
||||||
use editor::{scroll::autoscroll::Autoscroll, DisplayPoint, Editor};
|
use editor::{scroll::autoscroll::Autoscroll, DisplayPoint, Editor};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
executor::Deterministic, AssetSource, MutableAppContext, Task, TestAppContext, ViewHandle,
|
executor::Deterministic, AssetSource, MutableAppContext, TestAppContext, ViewHandle,
|
||||||
};
|
};
|
||||||
use language::LanguageRegistry;
|
use language::LanguageRegistry;
|
||||||
use project::{Project, ProjectPath};
|
use project::{Project, ProjectPath};
|
||||||
|
@ -1846,11 +1847,16 @@ mod tests {
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_bundled_languages(cx: &mut MutableAppContext) {
|
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());
|
languages.set_executor(cx.background().clone());
|
||||||
let languages = Arc::new(languages);
|
let languages = Arc::new(languages);
|
||||||
let themes = ThemeRegistry::new((), cx.font_cache().clone());
|
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() {
|
for name in languages.language_names() {
|
||||||
languages.language_for_name(&name);
|
languages.language_for_name(&name);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use serde::Deserialize;
|
||||||
#[import]
|
#[import]
|
||||||
fn command(string: &str) -> Option<Vec<u8>>;
|
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]
|
#[export]
|
||||||
pub fn name() -> &'static str {
|
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());
|
let version_dir = container_dir.join(version.as_str());
|
||||||
fs::create_dir_all(&version_dir)
|
fs::create_dir_all(&version_dir)
|
||||||
.map_err(|_| "failed to create version directory".to_string())?;
|
.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() {
|
if fs::metadata(&binary_path).is_err() {
|
||||||
let output = command(&format!(
|
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 last_version_dir = last_version_dir?;
|
||||||
let bin_path = last_version_dir.join(BIN_PATH);
|
let server_path = last_version_dir.join(SERVER_PATH);
|
||||||
if bin_path.exists() {
|
if server_path.exists() {
|
||||||
Some(bin_path)
|
Some(server_path)
|
||||||
} else {
|
} else {
|
||||||
println!("no binary found");
|
println!("no binary found");
|
||||||
None
|
None
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue