Detailed changes
@@ -0,0 +1,125 @@
+use client::http::{self, HttpClient, Method};
+use gpui::Task;
+pub use language::*;
+use rust_embed::RustEmbed;
+use serde::Deserialize;
+use std::{borrow::Cow, str, sync::Arc};
+
+mod c;
+mod json;
+mod rust;
+mod typescript;
+
+#[derive(RustEmbed)]
+#[folder = "src/languages"]
+#[exclude = "*.rs"]
+struct LanguageDir;
+
+#[derive(Deserialize)]
+struct GithubRelease {
+ name: String,
+ assets: Vec<GithubReleaseAsset>,
+}
+
+#[derive(Deserialize)]
+struct GithubReleaseAsset {
+ name: String,
+ browser_download_url: http::Url,
+}
+
+pub fn build_language_registry(login_shell_env_loaded: Task<()>) -> LanguageRegistry {
+ let languages = LanguageRegistry::new(login_shell_env_loaded);
+ for (name, grammar, lsp_adapter) in [
+ (
+ "c",
+ tree_sitter_c::language(),
+ Some(Arc::new(c::CLspAdapter) as Arc<dyn LspAdapter>),
+ ),
+ (
+ "json",
+ tree_sitter_json::language(),
+ Some(Arc::new(json::JsonLspAdapter)),
+ ),
+ (
+ "markdown",
+ tree_sitter_markdown::language(),
+ None, //
+ ),
+ (
+ "rust",
+ tree_sitter_rust::language(),
+ Some(Arc::new(rust::RustLspAdapter)),
+ ),
+ (
+ "tsx",
+ tree_sitter_typescript::language_tsx(),
+ None, //
+ ),
+ (
+ "typescript",
+ tree_sitter_typescript::language_typescript(),
+ Some(Arc::new(typescript::TypeScriptLspAdapter)),
+ ),
+ ] {
+ languages.add(Arc::new(language(name, grammar, lsp_adapter)));
+ }
+ languages
+}
+
+fn language(
+ name: &str,
+ grammar: tree_sitter::Language,
+ lsp_adapter: Option<Arc<dyn LspAdapter>>,
+) -> Language {
+ let config = toml::from_slice(
+ &LanguageDir::get(&format!("{}/config.toml", name))
+ .unwrap()
+ .data,
+ )
+ .unwrap();
+ let mut language = Language::new(config, Some(grammar));
+
+ if let Some(query) = load_query(name, "/highlights") {
+ language = language
+ .with_highlights_query(query.as_ref())
+ .expect("failed to evaluate highlights query");
+ }
+ if let Some(query) = load_query(name, "/brackets") {
+ language = language
+ .with_brackets_query(query.as_ref())
+ .expect("failed to load brackets query");
+ }
+ if let Some(query) = load_query(name, "/indents") {
+ language = language
+ .with_indents_query(query.as_ref())
+ .expect("failed to load indents query");
+ }
+ if let Some(query) = load_query(name, "/outline") {
+ language = language
+ .with_outline_query(query.as_ref())
+ .expect("failed to load outline query");
+ }
+ if let Some(lsp_adapter) = lsp_adapter {
+ language = language.with_lsp_adapter(lsp_adapter)
+ }
+ language
+}
+
+fn load_query(name: &str, filename_prefix: &str) -> Option<Cow<'static, str>> {
+ let mut result = None;
+ for path in LanguageDir::iter() {
+ if let Some(remainder) = path.strip_prefix(name) {
+ if remainder.starts_with(filename_prefix) {
+ let contents = match LanguageDir::get(path.as_ref()).unwrap().data {
+ Cow::Borrowed(s) => Cow::Borrowed(str::from_utf8(s).unwrap()),
+ Cow::Owned(s) => Cow::Owned(String::from_utf8(s).unwrap()),
+ };
+ match &mut result {
+ None => result = Some(contents),
+ Some(r) => r.to_mut().push_str(contents.as_ref()),
+ }
+ }
+ }
+ }
+ result
+}
@@ -0,0 +1,136 @@
+use anyhow::{anyhow, Result};
+use client::http::{self, HttpClient, Method};
+use futures::{future::BoxFuture, FutureExt, StreamExt};
+pub use language::*;
+use smol::fs::{self, File};
+use std::{path::PathBuf, str, sync::Arc};
+use util::{ResultExt, TryFutureExt};
+
+use super::GithubRelease;
+
+pub struct CLspAdapter;
+
+impl super::LspAdapter for CLspAdapter {
+ fn name(&self) -> &'static str {
+ "clangd"
+ }
+
+ fn fetch_latest_server_version(
+ &self,
+ http: Arc<dyn HttpClient>,
+ ) -> BoxFuture<'static, Result<LspBinaryVersion>> {
+ async move {
+ let release = http
+ .send(
+ surf::RequestBuilder::new(
+ Method::Get,
+ http::Url::parse(
+ "https://api.github.com/repos/clangd/clangd/releases/latest",
+ )
+ .unwrap(),
+ )
+ .middleware(surf::middleware::Redirect::default())
+ .build(),
+ )
+ .await
+ .map_err(|err| anyhow!("error fetching latest release: {}", err))?
+ .body_json::<GithubRelease>()
+ .await
+ .map_err(|err| anyhow!("error parsing latest release: {}", err))?;
+ let asset_name = format!("clangd-mac-{}.zip", release.name);
+ let asset = release
+ .assets
+ .iter()
+ .find(|asset| asset.name == asset_name)
+ .ok_or_else(|| anyhow!("no release found matching {:?}", asset_name))?;
+ Ok(LspBinaryVersion {
+ name: release.name,
+ url: Some(asset.browser_download_url.clone()),
+ })
+ }
+ .boxed()
+ }
+
+ fn fetch_server_binary(
+ &self,
+ version: LspBinaryVersion,
+ http: Arc<dyn HttpClient>,
+ container_dir: PathBuf,
+ ) -> BoxFuture<'static, Result<PathBuf>> {
+ async move {
+ let zip_path = container_dir.join(format!("clangd_{}.zip", version.name));
+ let version_dir = container_dir.join(format!("clangd_{}", version.name));
+ let binary_path = version_dir.join("bin/clangd");
+
+ if fs::metadata(&binary_path).await.is_err() {
+ let response = http
+ .send(
+ surf::RequestBuilder::new(Method::Get, version.url.unwrap())
+ .middleware(surf::middleware::Redirect::default())
+ .build(),
+ )
+ .await
+ .map_err(|err| anyhow!("error downloading release: {}", err))?;
+ let mut file = File::create(&zip_path).await?;
+ if !response.status().is_success() {
+ Err(anyhow!(
+ "download failed with status {}",
+ response.status().to_string()
+ ))?;
+ }
+ futures::io::copy(response, &mut file).await?;
+
+ let unzip_status = smol::process::Command::new("unzip")
+ .current_dir(&container_dir)
+ .arg(&zip_path)
+ .output()
+ .await?
+ .status;
+ if !unzip_status.success() {
+ Err(anyhow!("failed to unzip clangd archive"))?;
+ }
+
+ 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() != version_dir {
+ fs::remove_dir_all(&entry_path).await.log_err();
+ }
+ }
+ }
+ }
+ }
+
+ Ok(binary_path)
+ }
+ .boxed()
+ }
+
+ fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option<PathBuf>> {
+ async move {
+ let mut last_clangd_dir = None;
+ let mut entries = fs::read_dir(&container_dir).await?;
+ while let Some(entry) = entries.next().await {
+ let entry = entry?;
+ if entry.file_type().await?.is_dir() {
+ last_clangd_dir = Some(entry.path());
+ }
+ }
+ 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)
+ } else {
+ Err(anyhow!(
+ "missing clangd binary in directory {:?}",
+ clangd_dir
+ ))
+ }
+ }
+ .log_err()
+ .boxed()
+ }
+
+ fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
+}
@@ -0,0 +1,131 @@
+use anyhow::{anyhow, Context, Result};
+use client::http::HttpClient;
+use futures::{future::BoxFuture, FutureExt, StreamExt};
+use language::{LspAdapter, LspBinaryVersion};
+use serde::Deserialize;
+use serde_json::json;
+use smol::fs;
+use std::{path::PathBuf, sync::Arc};
+use util::ResultExt;
+
+pub struct JsonLspAdapter;
+
+impl JsonLspAdapter {
+ const BIN_PATH: &'static str =
+ "node_modules/vscode-json-languageserver/bin/vscode-json-languageserver";
+}
+
+impl LspAdapter for JsonLspAdapter {
+ fn name(&self) -> &'static str {
+ "vscode-json-languageserver"
+ }
+
+ fn server_args(&self) -> &[&str] {
+ &["--stdio"]
+ }
+
+ fn fetch_latest_server_version(
+ &self,
+ _: Arc<dyn HttpClient>,
+ ) -> BoxFuture<'static, Result<LspBinaryVersion>> {
+ async move {
+ #[derive(Deserialize)]
+ struct NpmInfo {
+ versions: Vec<String>,
+ }
+
+ let output = smol::process::Command::new("npm")
+ .args(["info", "vscode-json-languageserver", "--json"])
+ .output()
+ .await?;
+ if !output.status.success() {
+ Err(anyhow!("failed to execute npm info"))?;
+ }
+ let mut info: NpmInfo = serde_json::from_slice(&output.stdout)?;
+
+ Ok(LspBinaryVersion {
+ name: info
+ .versions
+ .pop()
+ .ok_or_else(|| anyhow!("no versions found in npm info"))?,
+ url: Default::default(),
+ })
+ }
+ .boxed()
+ }
+
+ fn fetch_server_binary(
+ &self,
+ version: LspBinaryVersion,
+ _: Arc<dyn HttpClient>,
+ container_dir: PathBuf,
+ ) -> BoxFuture<'static, Result<PathBuf>> {
+ async move {
+ let version_dir = container_dir.join(&version.name);
+ fs::create_dir_all(&version_dir)
+ .await
+ .context("failed to create version directory")?;
+ let binary_path = version_dir.join(Self::BIN_PATH);
+
+ if fs::metadata(&binary_path).await.is_err() {
+ let output = smol::process::Command::new("npm")
+ .current_dir(&version_dir)
+ .arg("install")
+ .arg(format!("vscode-json-languageserver@{}", version.name))
+ .output()
+ .await
+ .context("failed to run npm install")?;
+ if !output.status.success() {
+ Err(anyhow!("failed to install vscode-json-languageserver"))?;
+ }
+
+ 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() != version_dir {
+ fs::remove_dir_all(&entry_path).await.log_err();
+ }
+ }
+ }
+ }
+ }
+
+ Ok(binary_path)
+ }
+ .boxed()
+ }
+
+ fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option<PathBuf>> {
+ async move {
+ let mut last_version_dir = None;
+ let mut entries = fs::read_dir(&container_dir).await?;
+ while let Some(entry) = entries.next().await {
+ 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 bin_path = last_version_dir.join(Self::BIN_PATH);
+ if bin_path.exists() {
+ Ok(bin_path)
+ } else {
+ Err(anyhow!(
+ "missing executable in directory {:?}",
+ last_version_dir
+ ))
+ }
+ }
+ .log_err()
+ .boxed()
+ }
+
+ fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
+
+ fn initialization_options(&self) -> Option<serde_json::Value> {
+ Some(json!({
+ "provideFormatter": true
+ }))
+ }
+}
@@ -1,37 +1,17 @@
-use anyhow::{anyhow, Context, Result};
+use anyhow::{anyhow, Result};
use async_compression::futures::bufread::GzipDecoder;
use client::http::{self, HttpClient, Method};
use futures::{future::BoxFuture, FutureExt, StreamExt};
-use gpui::Task;
pub use language::*;
use lazy_static::lazy_static;
use regex::Regex;
-use rust_embed::RustEmbed;
-use serde::Deserialize;
-use serde_json::json;
use smol::fs::{self, File};
use std::{borrow::Cow, env::consts, path::PathBuf, str, sync::Arc};
use util::{ResultExt, TryFutureExt};
-#[derive(RustEmbed)]
-#[folder = "languages"]
-struct LanguageDir;
+use super::GithubRelease;
-struct RustLspAdapter;
-struct CLspAdapter;
-struct JsonLspAdapter;
-
-#[derive(Deserialize)]
-struct GithubRelease {
- name: String,
- assets: Vec<GithubReleaseAsset>,
-}
-
-#[derive(Deserialize)]
-struct GithubReleaseAsset {
- name: String,
- browser_download_url: http::Url,
-}
+pub struct RustLspAdapter;
impl LspAdapter for RustLspAdapter {
fn name(&self) -> &'static str {
@@ -287,353 +267,11 @@ impl LspAdapter for RustLspAdapter {
}
}
-impl LspAdapter for CLspAdapter {
- fn name(&self) -> &'static str {
- "clangd"
- }
-
- fn fetch_latest_server_version(
- &self,
- http: Arc<dyn HttpClient>,
- ) -> BoxFuture<'static, Result<LspBinaryVersion>> {
- async move {
- let release = http
- .send(
- surf::RequestBuilder::new(
- Method::Get,
- http::Url::parse(
- "https://api.github.com/repos/clangd/clangd/releases/latest",
- )
- .unwrap(),
- )
- .middleware(surf::middleware::Redirect::default())
- .build(),
- )
- .await
- .map_err(|err| anyhow!("error fetching latest release: {}", err))?
- .body_json::<GithubRelease>()
- .await
- .map_err(|err| anyhow!("error parsing latest release: {}", err))?;
- let asset_name = format!("clangd-mac-{}.zip", release.name);
- let asset = release
- .assets
- .iter()
- .find(|asset| asset.name == asset_name)
- .ok_or_else(|| anyhow!("no release found matching {:?}", asset_name))?;
- Ok(LspBinaryVersion {
- name: release.name,
- url: Some(asset.browser_download_url.clone()),
- })
- }
- .boxed()
- }
-
- fn fetch_server_binary(
- &self,
- version: LspBinaryVersion,
- http: Arc<dyn HttpClient>,
- container_dir: PathBuf,
- ) -> BoxFuture<'static, Result<PathBuf>> {
- async move {
- let zip_path = container_dir.join(format!("clangd_{}.zip", version.name));
- let version_dir = container_dir.join(format!("clangd_{}", version.name));
- let binary_path = version_dir.join("bin/clangd");
-
- if fs::metadata(&binary_path).await.is_err() {
- let response = http
- .send(
- surf::RequestBuilder::new(Method::Get, version.url.unwrap())
- .middleware(surf::middleware::Redirect::default())
- .build(),
- )
- .await
- .map_err(|err| anyhow!("error downloading release: {}", err))?;
- let mut file = File::create(&zip_path).await?;
- if !response.status().is_success() {
- Err(anyhow!(
- "download failed with status {}",
- response.status().to_string()
- ))?;
- }
- futures::io::copy(response, &mut file).await?;
-
- let unzip_status = smol::process::Command::new("unzip")
- .current_dir(&container_dir)
- .arg(&zip_path)
- .output()
- .await?
- .status;
- if !unzip_status.success() {
- Err(anyhow!("failed to unzip clangd archive"))?;
- }
-
- 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() != version_dir {
- fs::remove_dir_all(&entry_path).await.log_err();
- }
- }
- }
- }
- }
-
- Ok(binary_path)
- }
- .boxed()
- }
-
- fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option<PathBuf>> {
- async move {
- let mut last_clangd_dir = None;
- let mut entries = fs::read_dir(&container_dir).await?;
- while let Some(entry) = entries.next().await {
- let entry = entry?;
- if entry.file_type().await?.is_dir() {
- last_clangd_dir = Some(entry.path());
- }
- }
- 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)
- } else {
- Err(anyhow!(
- "missing clangd binary in directory {:?}",
- clangd_dir
- ))
- }
- }
- .log_err()
- .boxed()
- }
-
- fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
-}
-
-impl JsonLspAdapter {
- const BIN_PATH: &'static str =
- "node_modules/vscode-json-languageserver/bin/vscode-json-languageserver";
-}
-
-impl LspAdapter for JsonLspAdapter {
- fn name(&self) -> &'static str {
- "vscode-json-languageserver"
- }
-
- fn server_args(&self) -> &[&str] {
- &["--stdio"]
- }
-
- fn fetch_latest_server_version(
- &self,
- _: Arc<dyn HttpClient>,
- ) -> BoxFuture<'static, Result<LspBinaryVersion>> {
- async move {
- #[derive(Deserialize)]
- struct NpmInfo {
- versions: Vec<String>,
- }
-
- let output = smol::process::Command::new("npm")
- .args(["info", "vscode-json-languageserver", "--json"])
- .output()
- .await?;
- if !output.status.success() {
- Err(anyhow!("failed to execute npm info"))?;
- }
- let mut info: NpmInfo = serde_json::from_slice(&output.stdout)?;
-
- Ok(LspBinaryVersion {
- name: info
- .versions
- .pop()
- .ok_or_else(|| anyhow!("no versions found in npm info"))?,
- url: Default::default(),
- })
- }
- .boxed()
- }
-
- fn fetch_server_binary(
- &self,
- version: LspBinaryVersion,
- _: Arc<dyn HttpClient>,
- container_dir: PathBuf,
- ) -> BoxFuture<'static, Result<PathBuf>> {
- async move {
- let version_dir = container_dir.join(&version.name);
- fs::create_dir_all(&version_dir)
- .await
- .context("failed to create version directory")?;
- let binary_path = version_dir.join(Self::BIN_PATH);
-
- if fs::metadata(&binary_path).await.is_err() {
- let output = smol::process::Command::new("npm")
- .current_dir(&version_dir)
- .arg("install")
- .arg(format!("vscode-json-languageserver@{}", version.name))
- .output()
- .await
- .context("failed to run npm install")?;
- if !output.status.success() {
- Err(anyhow!("failed to install vscode-json-languageserver"))?;
- }
-
- 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() != version_dir {
- fs::remove_dir_all(&entry_path).await.log_err();
- }
- }
- }
- }
- }
-
- Ok(binary_path)
- }
- .boxed()
- }
-
- fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option<PathBuf>> {
- async move {
- let mut last_version_dir = None;
- let mut entries = fs::read_dir(&container_dir).await?;
- while let Some(entry) = entries.next().await {
- 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 bin_path = last_version_dir.join(Self::BIN_PATH);
- if bin_path.exists() {
- Ok(bin_path)
- } else {
- Err(anyhow!(
- "missing executable in directory {:?}",
- last_version_dir
- ))
- }
- }
- .log_err()
- .boxed()
- }
-
- fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
-
- fn initialization_options(&self) -> Option<serde_json::Value> {
- Some(json!({
- "provideFormatter": true
- }))
- }
-}
-
-pub fn build_language_registry(login_shell_env_loaded: Task<()>) -> LanguageRegistry {
- let languages = LanguageRegistry::new(login_shell_env_loaded);
- for (name, grammar, lsp_adapter) in [
- (
- "c",
- tree_sitter_c::language(),
- Some(Arc::new(CLspAdapter) as Arc<dyn LspAdapter>),
- ),
- (
- "json",
- tree_sitter_json::language(),
- Some(Arc::new(JsonLspAdapter)),
- ),
- (
- "markdown",
- tree_sitter_markdown::language(),
- None, //
- ),
- (
- "rust",
- tree_sitter_rust::language(),
- Some(Arc::new(RustLspAdapter)),
- ),
- (
- "tsx",
- tree_sitter_typescript::language_tsx(),
- None, //
- ),
- (
- "typescript",
- tree_sitter_typescript::language_typescript(),
- None, //
- ),
- ] {
- languages.add(Arc::new(language(name, grammar, lsp_adapter)));
- }
- languages
-}
-
-fn language(
- name: &str,
- grammar: tree_sitter::Language,
- lsp_adapter: Option<Arc<dyn LspAdapter>>,
-) -> Language {
- let config = toml::from_slice(
- &LanguageDir::get(&format!("{}/config.toml", name))
- .unwrap()
- .data,
- )
- .unwrap();
- let mut language = Language::new(config, Some(grammar));
-
- if let Some(query) = load_query(name, "/highlights") {
- language = language
- .with_highlights_query(query.as_ref())
- .expect("failed to evaluate highlights query");
- }
- if let Some(query) = load_query(name, "/brackets") {
- language = language
- .with_brackets_query(query.as_ref())
- .expect("failed to load brackets query");
- }
- if let Some(query) = load_query(name, "/indents") {
- language = language
- .with_indents_query(query.as_ref())
- .expect("failed to load indents query");
- }
- if let Some(query) = load_query(name, "/outline") {
- language = language
- .with_outline_query(query.as_ref())
- .expect("failed to load outline query");
- }
- if let Some(lsp_adapter) = lsp_adapter {
- language = language.with_lsp_adapter(lsp_adapter)
- }
- language
-}
-
-fn load_query(name: &str, filename_prefix: &str) -> Option<Cow<'static, str>> {
- let mut result = None;
- for path in LanguageDir::iter() {
- if let Some(remainder) = path.strip_prefix(name) {
- if remainder.starts_with(filename_prefix) {
- let contents = match LanguageDir::get(path.as_ref()).unwrap().data {
- Cow::Borrowed(s) => Cow::Borrowed(str::from_utf8(s).unwrap()),
- Cow::Owned(s) => Cow::Owned(String::from_utf8(s).unwrap()),
- };
- match &mut result {
- None => result = Some(contents),
- Some(r) => r.to_mut().push_str(contents.as_ref()),
- }
- }
- }
- }
- result
-}
-
#[cfg(test)]
mod tests {
use super::*;
+ use crate::languages::{language, LspAdapter};
use gpui::color::Color;
- use language::LspAdapter;
use theme::SyntaxTheme;
#[test]
@@ -0,0 +1,121 @@
+pub struct TypeScriptLspAdapter;
+
+impl TypeScriptLspAdapter {
+ const BIN_PATH: &'static str =
+ "node_modules/vscode-json-languageserver/bin/vscode-json-languageserver";
+}
+
+impl super::LspAdapter for TypeScriptLspAdapter {
+ fn name(&self) -> &'static str {
+ "typescript-language-server"
+ }
+
+ fn server_args(&self) -> &[&str] {
+ &["--stdio"]
+ }
+
+ fn fetch_latest_server_version(
+ &self,
+ _: Arc<dyn HttpClient>,
+ ) -> BoxFuture<'static, Result<LspBinaryVersion>> {
+ async move {
+ #[derive(Deserialize)]
+ struct NpmInfo {
+ versions: Vec<String>,
+ }
+
+ let output = smol::process::Command::new("npm")
+ .args(["info", "vscode-json-languageserver", "--json"])
+ .output()
+ .await?;
+ if !output.status.success() {
+ Err(anyhow!("failed to execute npm info"))?;
+ }
+ let mut info: NpmInfo = serde_json::from_slice(&output.stdout)?;
+
+ Ok(LspBinaryVersion {
+ name: info
+ .versions
+ .pop()
+ .ok_or_else(|| anyhow!("no versions found in npm info"))?,
+ url: Default::default(),
+ })
+ }
+ .boxed()
+ }
+
+ fn fetch_server_binary(
+ &self,
+ version: LspBinaryVersion,
+ _: Arc<dyn HttpClient>,
+ container_dir: PathBuf,
+ ) -> BoxFuture<'static, Result<PathBuf>> {
+ async move {
+ let version_dir = container_dir.join(&version.name);
+ fs::create_dir_all(&version_dir)
+ .await
+ .context("failed to create version directory")?;
+ let binary_path = version_dir.join(Self::BIN_PATH);
+
+ if fs::metadata(&binary_path).await.is_err() {
+ let output = smol::process::Command::new("npm")
+ .current_dir(&version_dir)
+ .arg("install")
+ .arg(format!("vscode-json-languageserver@{}", version.name))
+ .output()
+ .await
+ .context("failed to run npm install")?;
+ if !output.status.success() {
+ Err(anyhow!("failed to install vscode-json-languageserver"))?;
+ }
+
+ 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() != version_dir {
+ fs::remove_dir_all(&entry_path).await.log_err();
+ }
+ }
+ }
+ }
+ }
+
+ Ok(binary_path)
+ }
+ .boxed()
+ }
+
+ fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option<PathBuf>> {
+ async move {
+ let mut last_version_dir = None;
+ let mut entries = fs::read_dir(&container_dir).await?;
+ while let Some(entry) = entries.next().await {
+ 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 bin_path = last_version_dir.join(Self::BIN_PATH);
+ if bin_path.exists() {
+ Ok(bin_path)
+ } else {
+ Err(anyhow!(
+ "missing executable in directory {:?}",
+ last_version_dir
+ ))
+ }
+ }
+ .log_err()
+ .boxed()
+ }
+
+ fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
+
+ fn initialization_options(&self) -> Option<serde_json::Value> {
+ Some(json!({
+ "provideFormatter": true
+ }))
+ }
+}
@@ -19,7 +19,7 @@ use workspace::{
AppState, OpenNew, OpenParams, OpenPaths, Settings,
};
use zed::{
- self, assets::Assets, build_window_options, build_workspace, fs::RealFs, language, menus,
+ self, assets::Assets, build_window_options, build_workspace, fs::RealFs, languages, menus,
};
fn main() {
@@ -34,7 +34,7 @@ fn main() {
let default_settings = Settings::new("Zed Mono", &app.font_cache(), theme)
.unwrap()
.with_overrides(
- language::PLAIN_TEXT.name(),
+ languages::PLAIN_TEXT.name(),
settings::LanguageOverride {
soft_wrap: Some(settings::SoftWrap::PreferredLineLength),
..Default::default()
@@ -60,7 +60,7 @@ fn main() {
app.run(move |cx| {
let http = http::client();
let client = client::Client::new(http.clone());
- let mut languages = language::build_language_registry(login_shell_env_loaded);
+ let mut languages = languages::build_language_registry(login_shell_env_loaded);
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx));
let channel_list =
cx.add_model(|cx| ChannelList::new(user_store.clone(), client.clone(), cx));
@@ -1,5 +1,5 @@
pub mod assets;
-pub mod language;
+pub mod languages;
pub mod menus;
#[cfg(any(test, feature = "test-support"))]
pub mod test;
@@ -557,7 +557,7 @@ mod tests {
assert_eq!(editor.title(cx), "untitled");
assert!(Arc::ptr_eq(
editor.language(cx).unwrap(),
- &language::PLAIN_TEXT
+ &languages::PLAIN_TEXT
));
editor.handle_input(&editor::Input("hi".into()), cx);
assert!(editor.is_dirty(cx));
@@ -647,7 +647,7 @@ mod tests {
editor.update(cx, |editor, cx| {
assert!(Arc::ptr_eq(
editor.language(cx).unwrap(),
- &language::PLAIN_TEXT
+ &languages::PLAIN_TEXT
));
editor.handle_input(&editor::Input("hi".into()), cx);
assert!(editor.is_dirty(cx.as_ref()));