diff --git a/Cargo.lock b/Cargo.lock index 5808fddc9e..e66fa21f6e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9112,6 +9112,15 @@ dependencies = [ "wasmtime-c-api-impl", ] +[[package]] +name = "tree-sitter-astro" +version = "0.0.1" +source = "git+https://github.com/virchau13/tree-sitter-astro.git?rev=e924787e12e8a03194f36a113290ac11d6dc10f3#e924787e12e8a03194f36a113290ac11d6dc10f3" +dependencies = [ + "cc", + "tree-sitter", +] + [[package]] name = "tree-sitter-bash" version = "0.20.4" @@ -10846,6 +10855,7 @@ dependencies = [ "tiny_http", "toml", "tree-sitter", + "tree-sitter-astro", "tree-sitter-bash", "tree-sitter-beancount", "tree-sitter-c", diff --git a/Cargo.toml b/Cargo.toml index 4b936c4cfe..02c52bd077 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -223,6 +223,7 @@ tiktoken-rs = "0.5.7" time = { version = "0.3", features = ["serde", "serde-well-known"] } toml = "0.5" tree-sitter = { version = "0.20", features = ["wasm"] } +tree-sitter-astro = { git = "https://github.com/virchau13/tree-sitter-astro.git", rev = "e924787e12e8a03194f36a113290ac11d6dc10f3" } tree-sitter-bash = { git = "https://github.com/tree-sitter/tree-sitter-bash", rev = "7331995b19b8f8aba2d5e26deb51d2195c18bc94" } tree-sitter-beancount = { git = "https://github.com/polarmutex/tree-sitter-beancount", rev = "da1bf8c6eb0ae7a97588affde7227630bcd678b6" } tree-sitter-c = "0.20.1" diff --git a/assets/icons/file_icons/astro.svg b/assets/icons/file_icons/astro.svg new file mode 100644 index 0000000000..9b7815938d --- /dev/null +++ b/assets/icons/file_icons/astro.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/file_icons/file_types.json b/assets/icons/file_icons/file_types.json index 50e450c4c2..304137680f 100644 --- a/assets/icons/file_icons/file_types.json +++ b/assets/icons/file_icons/file_types.json @@ -1,5 +1,6 @@ { "suffixes": { + "astro": "astro", "Emakefile": "erlang", "aac": "audio", "accdb": "storage", @@ -148,6 +149,9 @@ "zshrc": "terminal" }, "types": { + "astro": { + "icon": "icons/file_icons/astro.svg" + }, "audio": { "icon": "icons/file_icons/audio.svg" }, diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index fc957ad559..bddbee4aac 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -108,6 +108,7 @@ theme_selector.workspace = true thiserror.workspace = true tiny_http = "0.8" toml.workspace = true +tree-sitter-astro.workspace = true tree-sitter-bash.workspace = true tree-sitter-beancount.workspace = true tree-sitter-c-sharp.workspace = true diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 59e4be70d7..418479962d 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -9,6 +9,7 @@ use util::asset_str; use self::{deno::DenoSettings, elixir::ElixirSettings}; +mod astro; mod c; mod clojure; mod csharp; @@ -65,6 +66,7 @@ pub fn init( DenoSettings::register(cx); languages.register_native_grammars([ + ("astro", tree_sitter_astro::language()), ("bash", tree_sitter_bash::language()), ("beancount", tree_sitter_beancount::language()), ("c", tree_sitter_c::language()), @@ -130,6 +132,13 @@ pub fn init( ) }; + language( + "astro", + vec![ + Arc::new(astro::AstroLspAdapter::new(node_runtime.clone())), + Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())), + ], + ); language("bash", vec![]); language("beancount", vec![]); language("c", vec![Arc::new(c::CLspAdapter) as Arc]); diff --git a/crates/zed/src/languages/astro.rs b/crates/zed/src/languages/astro.rs new file mode 100644 index 0000000000..88fd30950b --- /dev/null +++ b/crates/zed/src/languages/astro.rs @@ -0,0 +1,136 @@ +use anyhow::{anyhow, Result}; +use async_trait::async_trait; +use futures::StreamExt; +use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use lsp::LanguageServerBinary; +use node_runtime::NodeRuntime; +use serde_json::json; +use smol::fs; +use std::{ + any::Any, + ffi::OsString, + path::{Path, PathBuf}, + sync::Arc, +}; +use util::ResultExt; + +const SERVER_PATH: &'static str = "node_modules/@astrojs/language-server/bin/nodeServer.js"; + +fn server_binary_arguments(server_path: &Path) -> Vec { + vec![server_path.into(), "--stdio".into()] +} + +pub struct AstroLspAdapter { + node: Arc, +} + +impl AstroLspAdapter { + pub fn new(node: Arc) -> Self { + AstroLspAdapter { node } + } +} + +#[async_trait] +impl LspAdapter for AstroLspAdapter { + fn name(&self) -> LanguageServerName { + LanguageServerName("astro-language-server".into()) + } + + fn short_name(&self) -> &'static str { + "astro" + } + + async fn fetch_latest_server_version( + &self, + _: &dyn LspAdapterDelegate, + ) -> Result> { + Ok(Box::new( + self.node + .npm_package_latest_version("@astrojs/language-server") + .await?, + ) as Box<_>) + } + + async fn fetch_server_binary( + &self, + version: Box, + container_dir: PathBuf, + _: &dyn LspAdapterDelegate, + ) -> Result { + let version = version.downcast::().unwrap(); + let server_path = container_dir.join(SERVER_PATH); + + if fs::metadata(&server_path).await.is_err() { + self.node + .npm_install_packages( + &container_dir, + &[("@astrojs/language-server", version.as_str())], + ) + .await?; + } + + Ok(LanguageServerBinary { + path: self.node.binary_path().await?, + arguments: server_binary_arguments(&server_path), + }) + } + + async fn cached_server_binary( + &self, + container_dir: PathBuf, + _: &dyn LspAdapterDelegate, + ) -> Option { + get_cached_server_binary(container_dir, &*self.node).await + } + + async fn installation_test_binary( + &self, + container_dir: PathBuf, + ) -> Option { + get_cached_server_binary(container_dir, &*self.node).await + } + + fn initialization_options(&self) -> Option { + Some(json!({ + "provideFormatter": true, + "typescript": { + "tsdk": "node_modules/typescript/lib", + } + })) + } + + fn prettier_plugins(&self) -> &[&'static str] { + &["prettier-plugin-astro"] + } +} + +async fn get_cached_server_binary( + container_dir: PathBuf, + node: &dyn NodeRuntime, +) -> 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 server_path = last_version_dir.join(SERVER_PATH); + if server_path.exists() { + Ok(LanguageServerBinary { + path: node.binary_path().await?, + arguments: server_binary_arguments(&server_path), + }) + } else { + Err(anyhow!( + "missing executable in directory {:?}", + last_version_dir + )) + } + })() + .await + .log_err() +} diff --git a/crates/zed/src/languages/astro/brackets.scm b/crates/zed/src/languages/astro/brackets.scm new file mode 100644 index 0000000000..2221eda358 --- /dev/null +++ b/crates/zed/src/languages/astro/brackets.scm @@ -0,0 +1,3 @@ +("{" @open "}" @close) +("<" @open ">" @close) +("\"" @open "\"" @close) diff --git a/crates/zed/src/languages/astro/config.toml b/crates/zed/src/languages/astro/config.toml new file mode 100644 index 0000000000..e9f4b3d043 --- /dev/null +++ b/crates/zed/src/languages/astro/config.toml @@ -0,0 +1,22 @@ +name = "Astro" +grammar = "astro" +path_suffixes = ["astro"] +block_comment = [""] +autoclose_before = ";:.,=}])>" +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, + { start = "<", end = ">", close = false, newline = true, not_in = ["string", "comment"] }, + { start = "\"", end = "\"", close = true, newline = false, not_in = ["string", "comment"] }, + { start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] }, + { start = "`", end = "`", close = true, newline = false, not_in = ["string"] }, + { start = "/*", end = " */", close = true, newline = false, not_in = ["string", "comment"] }, +] +word_characters = ["#", "$", "-"] +scope_opt_in_language_servers = ["tailwindcss-language-server"] +prettier_parser_name = "astro" + +[overrides.string] +word_characters = ["-"] +opt_into_language_servers = ["tailwindcss-language-server"] diff --git a/crates/zed/src/languages/astro/highlights.scm b/crates/zed/src/languages/astro/highlights.scm new file mode 100644 index 0000000000..491e8cc337 --- /dev/null +++ b/crates/zed/src/languages/astro/highlights.scm @@ -0,0 +1,25 @@ +(tag_name) @tag +(erroneous_end_tag_name) @keyword +(doctype) @constant +(attribute_name) @property +(attribute_value) @string +(comment) @comment + +[ + (attribute_value) + (quoted_attribute_value) +] @string + +"=" @operator + +[ + "{" + "}" +] @punctuation.bracket + +[ + "<" + ">" + "" +] @tag.delimiter diff --git a/crates/zed/src/languages/astro/injections.scm b/crates/zed/src/languages/astro/injections.scm new file mode 100644 index 0000000000..109ccb9778 --- /dev/null +++ b/crates/zed/src/languages/astro/injections.scm @@ -0,0 +1,16 @@ +; inherits: html_tags +(frontmatter + (raw_text) @content + (#set! "language" "typescript")) + +(interpolation + (raw_text) @content + (#set! "language" "tsx")) + +(script_element + (raw_text) @content + (#set! "language" "typescript")) + +(style_element + (raw_text) @content + (#set! "language" "css")) diff --git a/crates/zed/src/languages/tailwind.rs b/crates/zed/src/languages/tailwind.rs index 6243db96eb..206e390e42 100644 --- a/crates/zed/src/languages/tailwind.rs +++ b/crates/zed/src/languages/tailwind.rs @@ -114,6 +114,7 @@ impl LspAdapter for TailwindLspAdapter { fn language_ids(&self) -> HashMap { HashMap::from_iter([ + ("Astro".to_string(), "astro".to_string()), ("HTML".to_string(), "html".to_string()), ("CSS".to_string(), "css".to_string()), ("JavaScript".to_string(), "javascript".to_string()), diff --git a/docs/src/languages/astro.md b/docs/src/languages/astro.md new file mode 100644 index 0000000000..6ce15792b3 --- /dev/null +++ b/docs/src/languages/astro.md @@ -0,0 +1,4 @@ +# Astro + +- Tree Sitter: [tree-sitter-astro](https://github.com/virchau13/tree-sitter-astro) +- Language Server: [astro](https://github.com/withastro/language-tools/tree/main/packages/language-server)