diff --git a/Cargo.lock b/Cargo.lock index 363ee93c14adde698e2d2b578c275d6c517ce7ef..5023de1f754660cb7312763390e022f64eb41244 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6043,6 +6043,16 @@ dependencies = [ "tree-sitter", ] +[[package]] +name = "tree-sitter-html" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "184e6b77953a354303dc87bf5fe36558c83569ce92606e7b382a0dc1b7443443" +dependencies = [ + "cc", + "tree-sitter", +] + [[package]] name = "tree-sitter-json" version = "0.19.0" @@ -7229,6 +7239,7 @@ dependencies = [ "tree-sitter-cpp", "tree-sitter-elixir", "tree-sitter-go", + "tree-sitter-html", "tree-sitter-json 0.20.0", "tree-sitter-markdown", "tree-sitter-python", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index dc2b0abd03e59e753f13eb323a372e4c817ad079..f84c8836bdf441101f691422c75eaf5934e7552d 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -100,6 +100,7 @@ tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", tree-sitter-python = "0.20.2" tree-sitter-toml = { git = "https://github.com/tree-sitter/tree-sitter-toml", rev = "342d9be207c2dba869b9967124c679b5e6fd0ebe" } tree-sitter-typescript = "0.20.1" +tree-sitter-html = "0.19.0" url = "2.2" [dev-dependencies] diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 6e57106e87b84212627563b59c46c73b8ab3c0b4..ba1945f3161a9aab42df6fd1e9b38ec0e08ff974 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -7,6 +7,7 @@ use std::{borrow::Cow, str, sync::Arc}; mod c; mod elixir; mod go; +mod html; mod installation; mod json; mod language_plugin; @@ -96,6 +97,11 @@ pub async fn init(languages: Arc, _executor: Arc) tree_sitter_typescript::language_tsx(), Some(CachedLspAdapter::new(typescript::TypeScriptLspAdapter).await), ), + ( + "html", + tree_sitter_html::language(), + Some(CachedLspAdapter::new(html::HtmlLspAdapter).await), + ), ] { languages.add(Arc::new(language(name, grammar, lsp_adapter))); } diff --git a/crates/zed/src/languages/html.rs b/crates/zed/src/languages/html.rs new file mode 100644 index 0000000000000000000000000000000000000000..5497841d886939a3e55a5dbe2a12ea6f07045f87 --- /dev/null +++ b/crates/zed/src/languages/html.rs @@ -0,0 +1,101 @@ +use super::installation::{npm_install_packages, npm_package_latest_version}; +use anyhow::{anyhow, Context, Result}; +use async_trait::async_trait; +use client::http::HttpClient; +use futures::StreamExt; +use language::{LanguageServerName, LspAdapter}; +use serde_json::json; +use smol::fs; +use std::{any::Any, path::PathBuf, sync::Arc}; +use util::ResultExt; + +pub struct HtmlLspAdapter; + +impl HtmlLspAdapter { + const BIN_PATH: &'static str = + "node_modules/vscode-langservers-extracted/bin/vscode-html-language-server"; +} + +#[async_trait] +impl LspAdapter for HtmlLspAdapter { + async fn name(&self) -> LanguageServerName { + LanguageServerName("vscode-html-language-server".into()) + } + + async fn server_args(&self) -> Vec { + vec!["--stdio".into()] + } + + async fn fetch_latest_server_version( + &self, + _: Arc, + ) -> Result> { + Ok(Box::new(npm_package_latest_version("vscode-langservers-extracted").await?) as Box<_>) + } + + async fn fetch_server_binary( + &self, + version: Box, + _: Arc, + container_dir: PathBuf, + ) -> Result { + let version = version.downcast::().unwrap(); + let version_dir = container_dir.join(version.as_str()); + fs::create_dir_all(&version_dir) + .await + .context("failed to create version directory")?; + let binary_path = version_dir.join(Self::BIN_PATH); + + if fs::metadata(&binary_path).await.is_err() { + npm_install_packages( + [("vscode-langservers-extracted", version.as_str())], + &version_dir, + ) + .await?; + + if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() { + while let Some(entry) = entries.next().await { + 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) + } + + async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { + (|| async move { + let mut last_version_dir = None; + let mut entries = fs::read_dir(&container_dir).await?; + 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 + )) + } + })() + .await + .log_err() + } + + async fn initialization_options(&self) -> Option { + Some(json!({ + "provideFormatter": true + })) + } +} diff --git a/crates/zed/src/languages/html/brackets.scm b/crates/zed/src/languages/html/brackets.scm new file mode 100644 index 0000000000000000000000000000000000000000..2d12b17daab82f7209c3b32cc4047d805e3b2415 --- /dev/null +++ b/crates/zed/src/languages/html/brackets.scm @@ -0,0 +1,2 @@ +("<" @open ">" @close) +("\"" @open "\"" @close) diff --git a/crates/zed/src/languages/html/config.toml b/crates/zed/src/languages/html/config.toml new file mode 100644 index 0000000000000000000000000000000000000000..0680717b2cda9c11134d69eedf9563ac010f8cc9 --- /dev/null +++ b/crates/zed/src/languages/html/config.toml @@ -0,0 +1,10 @@ +name = "HTML" +path_suffixes = ["html"] +autoclose_before = ">" +brackets = [ + { start = "<", end = ">", close = true, newline = true }, + { start = "{", end = "}", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, + { start = "\"", end = "\"", close = true, newline = false }, + { start = "!--", end = " --", close = true, newline = false }, +] diff --git a/crates/zed/src/languages/html/highlights.scm b/crates/zed/src/languages/html/highlights.scm new file mode 100644 index 0000000000000000000000000000000000000000..d6d361ca49d8b00b2024ed58512925f9a54dedbd --- /dev/null +++ b/crates/zed/src/languages/html/highlights.scm @@ -0,0 +1,11 @@ +(tag_name) @keyword +(doctype) @constant +(attribute_name) @property +(attribute_value) @string +(comment) @comment + +[ + "<" + ">" + "