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)