From d466768eed098a2185d68b6d5b19daa57175a6fc Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 29 Mar 2022 11:06:08 -0700 Subject: [PATCH] WIP --- crates/zed/src/languages.rs | 125 ++++++ crates/zed/src/languages/c.rs | 136 +++++++ crates/zed/{ => src}/languages/c/brackets.scm | 0 crates/zed/{ => src}/languages/c/config.toml | 0 .../zed/{ => src}/languages/c/highlights.scm | 0 crates/zed/{ => src}/languages/c/indents.scm | 0 crates/zed/{ => src}/languages/c/outline.scm | 0 crates/zed/src/languages/json.rs | 131 +++++++ .../zed/{ => src}/languages/json/brackets.scm | 0 .../zed/{ => src}/languages/json/config.toml | 0 .../{ => src}/languages/json/highlights.scm | 0 .../zed/{ => src}/languages/json/indents.scm | 0 .../zed/{ => src}/languages/json/outline.scm | 0 .../{ => src}/languages/markdown/config.toml | 0 .../languages/markdown/highlights.scm | 0 .../src/{language.rs => languages/rust.rs} | 370 +----------------- .../zed/{ => src}/languages/rust/brackets.scm | 0 .../zed/{ => src}/languages/rust/config.toml | 0 .../{ => src}/languages/rust/highlights.scm | 0 .../zed/{ => src}/languages/rust/indents.scm | 0 .../zed/{ => src}/languages/rust/outline.scm | 0 .../zed/{ => src}/languages/tsx/brackets.scm | 0 .../zed/{ => src}/languages/tsx/config.toml | 0 .../languages/tsx/highlights-jsx.scm | 0 .../{ => src}/languages/tsx/highlights.scm | 0 .../zed/{ => src}/languages/tsx/indents.scm | 0 .../zed/{ => src}/languages/tsx/outline.scm | 0 crates/zed/src/languages/typescript.rs | 121 ++++++ .../languages/typescript/brackets.scm | 0 .../languages/typescript/config.toml | 0 .../languages/typescript/highlights.scm | 0 .../languages/typescript/indents.scm | 0 .../languages/typescript/outline.scm | 0 crates/zed/src/main.rs | 6 +- crates/zed/src/zed.rs | 6 +- 35 files changed, 523 insertions(+), 372 deletions(-) create mode 100644 crates/zed/src/languages.rs create mode 100644 crates/zed/src/languages/c.rs rename crates/zed/{ => src}/languages/c/brackets.scm (100%) rename crates/zed/{ => src}/languages/c/config.toml (100%) rename crates/zed/{ => src}/languages/c/highlights.scm (100%) rename crates/zed/{ => src}/languages/c/indents.scm (100%) rename crates/zed/{ => src}/languages/c/outline.scm (100%) create mode 100644 crates/zed/src/languages/json.rs rename crates/zed/{ => src}/languages/json/brackets.scm (100%) rename crates/zed/{ => src}/languages/json/config.toml (100%) rename crates/zed/{ => src}/languages/json/highlights.scm (100%) rename crates/zed/{ => src}/languages/json/indents.scm (100%) rename crates/zed/{ => src}/languages/json/outline.scm (100%) rename crates/zed/{ => src}/languages/markdown/config.toml (100%) rename crates/zed/{ => src}/languages/markdown/highlights.scm (100%) rename crates/zed/src/{language.rs => languages/rust.rs} (57%) rename crates/zed/{ => src}/languages/rust/brackets.scm (100%) rename crates/zed/{ => src}/languages/rust/config.toml (100%) rename crates/zed/{ => src}/languages/rust/highlights.scm (100%) rename crates/zed/{ => src}/languages/rust/indents.scm (100%) rename crates/zed/{ => src}/languages/rust/outline.scm (100%) rename crates/zed/{ => src}/languages/tsx/brackets.scm (100%) rename crates/zed/{ => src}/languages/tsx/config.toml (100%) rename crates/zed/{ => src}/languages/tsx/highlights-jsx.scm (100%) rename crates/zed/{ => src}/languages/tsx/highlights.scm (100%) rename crates/zed/{ => src}/languages/tsx/indents.scm (100%) rename crates/zed/{ => src}/languages/tsx/outline.scm (100%) create mode 100644 crates/zed/src/languages/typescript.rs rename crates/zed/{ => src}/languages/typescript/brackets.scm (100%) rename crates/zed/{ => src}/languages/typescript/config.toml (100%) rename crates/zed/{ => src}/languages/typescript/highlights.scm (100%) rename crates/zed/{ => src}/languages/typescript/indents.scm (100%) rename crates/zed/{ => src}/languages/typescript/outline.scm (100%) diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs new file mode 100644 index 0000000000000000000000000000000000000000..607f4c30bb756935ce315a0378ce4caf1eb16b71 --- /dev/null +++ b/crates/zed/src/languages.rs @@ -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, +} + +#[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), + ), + ( + "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>, +) -> 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> { + 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 +} diff --git a/crates/zed/src/languages/c.rs b/crates/zed/src/languages/c.rs new file mode 100644 index 0000000000000000000000000000000000000000..9ce3eab2f772d86fdcc7f7a717020480660a5ed5 --- /dev/null +++ b/crates/zed/src/languages/c.rs @@ -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, + ) -> BoxFuture<'static, Result> { + 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::() + .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, + container_dir: PathBuf, + ) -> BoxFuture<'static, Result> { + 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> { + 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) {} +} diff --git a/crates/zed/languages/c/brackets.scm b/crates/zed/src/languages/c/brackets.scm similarity index 100% rename from crates/zed/languages/c/brackets.scm rename to crates/zed/src/languages/c/brackets.scm diff --git a/crates/zed/languages/c/config.toml b/crates/zed/src/languages/c/config.toml similarity index 100% rename from crates/zed/languages/c/config.toml rename to crates/zed/src/languages/c/config.toml diff --git a/crates/zed/languages/c/highlights.scm b/crates/zed/src/languages/c/highlights.scm similarity index 100% rename from crates/zed/languages/c/highlights.scm rename to crates/zed/src/languages/c/highlights.scm diff --git a/crates/zed/languages/c/indents.scm b/crates/zed/src/languages/c/indents.scm similarity index 100% rename from crates/zed/languages/c/indents.scm rename to crates/zed/src/languages/c/indents.scm diff --git a/crates/zed/languages/c/outline.scm b/crates/zed/src/languages/c/outline.scm similarity index 100% rename from crates/zed/languages/c/outline.scm rename to crates/zed/src/languages/c/outline.scm diff --git a/crates/zed/src/languages/json.rs b/crates/zed/src/languages/json.rs new file mode 100644 index 0000000000000000000000000000000000000000..bb7744714f415a4db7ee1df25cb0f65027a371b7 --- /dev/null +++ b/crates/zed/src/languages/json.rs @@ -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, + ) -> BoxFuture<'static, Result> { + async move { + #[derive(Deserialize)] + struct NpmInfo { + versions: Vec, + } + + 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, + container_dir: PathBuf, + ) -> BoxFuture<'static, Result> { + 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> { + 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 { + Some(json!({ + "provideFormatter": true + })) + } +} diff --git a/crates/zed/languages/json/brackets.scm b/crates/zed/src/languages/json/brackets.scm similarity index 100% rename from crates/zed/languages/json/brackets.scm rename to crates/zed/src/languages/json/brackets.scm diff --git a/crates/zed/languages/json/config.toml b/crates/zed/src/languages/json/config.toml similarity index 100% rename from crates/zed/languages/json/config.toml rename to crates/zed/src/languages/json/config.toml diff --git a/crates/zed/languages/json/highlights.scm b/crates/zed/src/languages/json/highlights.scm similarity index 100% rename from crates/zed/languages/json/highlights.scm rename to crates/zed/src/languages/json/highlights.scm diff --git a/crates/zed/languages/json/indents.scm b/crates/zed/src/languages/json/indents.scm similarity index 100% rename from crates/zed/languages/json/indents.scm rename to crates/zed/src/languages/json/indents.scm diff --git a/crates/zed/languages/json/outline.scm b/crates/zed/src/languages/json/outline.scm similarity index 100% rename from crates/zed/languages/json/outline.scm rename to crates/zed/src/languages/json/outline.scm diff --git a/crates/zed/languages/markdown/config.toml b/crates/zed/src/languages/markdown/config.toml similarity index 100% rename from crates/zed/languages/markdown/config.toml rename to crates/zed/src/languages/markdown/config.toml diff --git a/crates/zed/languages/markdown/highlights.scm b/crates/zed/src/languages/markdown/highlights.scm similarity index 100% rename from crates/zed/languages/markdown/highlights.scm rename to crates/zed/src/languages/markdown/highlights.scm diff --git a/crates/zed/src/language.rs b/crates/zed/src/languages/rust.rs similarity index 57% rename from crates/zed/src/language.rs rename to crates/zed/src/languages/rust.rs index 50540bc82cb5a6059b4fc2a02a1a3f33e3d741fc..2d4c6b3d3673c1117abbe86fcfc48044f283cf05 100644 --- a/crates/zed/src/language.rs +++ b/crates/zed/src/languages/rust.rs @@ -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, -} - -#[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, - ) -> BoxFuture<'static, Result> { - 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::() - .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, - container_dir: PathBuf, - ) -> BoxFuture<'static, Result> { - 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> { - 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, - ) -> BoxFuture<'static, Result> { - async move { - #[derive(Deserialize)] - struct NpmInfo { - versions: Vec, - } - - 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, - container_dir: PathBuf, - ) -> BoxFuture<'static, Result> { - 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> { - 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 { - 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), - ), - ( - "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>, -) -> 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> { - 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] diff --git a/crates/zed/languages/rust/brackets.scm b/crates/zed/src/languages/rust/brackets.scm similarity index 100% rename from crates/zed/languages/rust/brackets.scm rename to crates/zed/src/languages/rust/brackets.scm diff --git a/crates/zed/languages/rust/config.toml b/crates/zed/src/languages/rust/config.toml similarity index 100% rename from crates/zed/languages/rust/config.toml rename to crates/zed/src/languages/rust/config.toml diff --git a/crates/zed/languages/rust/highlights.scm b/crates/zed/src/languages/rust/highlights.scm similarity index 100% rename from crates/zed/languages/rust/highlights.scm rename to crates/zed/src/languages/rust/highlights.scm diff --git a/crates/zed/languages/rust/indents.scm b/crates/zed/src/languages/rust/indents.scm similarity index 100% rename from crates/zed/languages/rust/indents.scm rename to crates/zed/src/languages/rust/indents.scm diff --git a/crates/zed/languages/rust/outline.scm b/crates/zed/src/languages/rust/outline.scm similarity index 100% rename from crates/zed/languages/rust/outline.scm rename to crates/zed/src/languages/rust/outline.scm diff --git a/crates/zed/languages/tsx/brackets.scm b/crates/zed/src/languages/tsx/brackets.scm similarity index 100% rename from crates/zed/languages/tsx/brackets.scm rename to crates/zed/src/languages/tsx/brackets.scm diff --git a/crates/zed/languages/tsx/config.toml b/crates/zed/src/languages/tsx/config.toml similarity index 100% rename from crates/zed/languages/tsx/config.toml rename to crates/zed/src/languages/tsx/config.toml diff --git a/crates/zed/languages/tsx/highlights-jsx.scm b/crates/zed/src/languages/tsx/highlights-jsx.scm similarity index 100% rename from crates/zed/languages/tsx/highlights-jsx.scm rename to crates/zed/src/languages/tsx/highlights-jsx.scm diff --git a/crates/zed/languages/tsx/highlights.scm b/crates/zed/src/languages/tsx/highlights.scm similarity index 100% rename from crates/zed/languages/tsx/highlights.scm rename to crates/zed/src/languages/tsx/highlights.scm diff --git a/crates/zed/languages/tsx/indents.scm b/crates/zed/src/languages/tsx/indents.scm similarity index 100% rename from crates/zed/languages/tsx/indents.scm rename to crates/zed/src/languages/tsx/indents.scm diff --git a/crates/zed/languages/tsx/outline.scm b/crates/zed/src/languages/tsx/outline.scm similarity index 100% rename from crates/zed/languages/tsx/outline.scm rename to crates/zed/src/languages/tsx/outline.scm diff --git a/crates/zed/src/languages/typescript.rs b/crates/zed/src/languages/typescript.rs new file mode 100644 index 0000000000000000000000000000000000000000..59b7d225f9cb4dc1850e3a42280011dd37d7454d --- /dev/null +++ b/crates/zed/src/languages/typescript.rs @@ -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, + ) -> BoxFuture<'static, Result> { + async move { + #[derive(Deserialize)] + struct NpmInfo { + versions: Vec, + } + + 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, + container_dir: PathBuf, + ) -> BoxFuture<'static, Result> { + 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> { + 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 { + Some(json!({ + "provideFormatter": true + })) + } +} diff --git a/crates/zed/languages/typescript/brackets.scm b/crates/zed/src/languages/typescript/brackets.scm similarity index 100% rename from crates/zed/languages/typescript/brackets.scm rename to crates/zed/src/languages/typescript/brackets.scm diff --git a/crates/zed/languages/typescript/config.toml b/crates/zed/src/languages/typescript/config.toml similarity index 100% rename from crates/zed/languages/typescript/config.toml rename to crates/zed/src/languages/typescript/config.toml diff --git a/crates/zed/languages/typescript/highlights.scm b/crates/zed/src/languages/typescript/highlights.scm similarity index 100% rename from crates/zed/languages/typescript/highlights.scm rename to crates/zed/src/languages/typescript/highlights.scm diff --git a/crates/zed/languages/typescript/indents.scm b/crates/zed/src/languages/typescript/indents.scm similarity index 100% rename from crates/zed/languages/typescript/indents.scm rename to crates/zed/src/languages/typescript/indents.scm diff --git a/crates/zed/languages/typescript/outline.scm b/crates/zed/src/languages/typescript/outline.scm similarity index 100% rename from crates/zed/languages/typescript/outline.scm rename to crates/zed/src/languages/typescript/outline.scm diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 63721346c399ca60ea4f701f330ad78fb61d63e0..49efc9ade2af2749cca42cc20362a1c174dea833 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -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)); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 1302d54067810653140367b40966e869edcf8372..25aa011c9b5ec801a79f8e7a5720904e862a6b4a 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -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()));