Add Heex language

Max Brunsfeld created

Change summary

Cargo.lock                                     |  10 
crates/zed/Cargo.toml                          |   1 
crates/zed/src/languages.rs                    | 205 +++++++++----------
crates/zed/src/languages/elixir/injections.scm |   7 
crates/zed/src/languages/erb/highlights.scm    |   2 
crates/zed/src/languages/heex/config.toml      |   7 
crates/zed/src/languages/heex/highlights.scm   |  54 +++++
crates/zed/src/languages/heex/injections.scm   |  13 +
8 files changed, 195 insertions(+), 104 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -7423,6 +7423,15 @@ dependencies = [
  "tree-sitter",
 ]
 
+[[package]]
+name = "tree-sitter-heex"
+version = "0.0.1"
+source = "git+https://github.com/phoenixframework/tree-sitter-heex?rev=2e1348c3cf2c9323e87c2744796cf3f3868aa82a#2e1348c3cf2c9323e87c2744796cf3f3868aa82a"
+dependencies = [
+ "cc",
+ "tree-sitter",
+]
+
 [[package]]
 name = "tree-sitter-html"
 version = "0.19.0"
@@ -8876,6 +8885,7 @@ dependencies = [
  "tree-sitter-elixir",
  "tree-sitter-embedded-template",
  "tree-sitter-go",
+ "tree-sitter-heex",
  "tree-sitter-html",
  "tree-sitter-json 0.20.0",
  "tree-sitter-lua",

crates/zed/Cargo.toml 🔗

@@ -109,6 +109,7 @@ tree-sitter-css = { git = "https://github.com/tree-sitter/tree-sitter-css", rev
 tree-sitter-elixir = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "4ba9dab6e2602960d95b2b625f3386c27e08084e" }
 tree-sitter-embedded-template = "0.20.0"
 tree-sitter-go = { git = "https://github.com/tree-sitter/tree-sitter-go", rev = "aeb2f33b366fd78d5789ff104956ce23508b85db" }
+tree-sitter-heex = { git = "https://github.com/phoenixframework/tree-sitter-heex", rev = "2e1348c3cf2c9323e87c2744796cf3f3868aa82a" }
 tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "40a81c01a40ac48744e0c8ccabbaba1920441199" }
 tree-sitter-rust = "0.20.3"
 tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" }

crates/zed/src/languages.rs 🔗

@@ -34,110 +34,109 @@ mod yaml;
 struct LanguageDir;
 
 pub fn init(languages: Arc<LanguageRegistry>, node_runtime: Arc<NodeRuntime>) {
-    fn adapter_arc(adapter: impl LspAdapter) -> Arc<dyn LspAdapter> {
-        Arc::new(adapter)
-    }
-
-    let languages_list = [
-        (
-            "c",
-            tree_sitter_c::language(),
-            vec![adapter_arc(c::CLspAdapter)],
-        ),
-        (
-            "cpp",
-            tree_sitter_cpp::language(),
-            vec![adapter_arc(c::CLspAdapter)],
-        ),
-        ("css", tree_sitter_css::language(), vec![]),
-        (
-            "elixir",
-            tree_sitter_elixir::language(),
-            vec![adapter_arc(elixir::ElixirLspAdapter)],
-        ),
-        (
-            "go",
-            tree_sitter_go::language(),
-            vec![adapter_arc(go::GoLspAdapter)],
-        ),
-        (
-            "json",
-            tree_sitter_json::language(),
-            vec![adapter_arc(json::JsonLspAdapter::new(
-                node_runtime.clone(),
-                languages.clone(),
-            ))],
-        ),
-        ("markdown", tree_sitter_markdown::language(), vec![]),
-        (
-            "python",
-            tree_sitter_python::language(),
-            vec![adapter_arc(python::PythonLspAdapter::new(
-                node_runtime.clone(),
-            ))],
-        ),
-        (
-            "rust",
-            tree_sitter_rust::language(),
-            vec![adapter_arc(rust::RustLspAdapter)],
-        ),
-        ("toml", tree_sitter_toml::language(), vec![]),
-        (
-            "tsx",
-            tree_sitter_typescript::language_tsx(),
-            vec![
-                adapter_arc(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
-                adapter_arc(typescript::EsLintLspAdapter::new(node_runtime.clone())),
-            ],
-        ),
-        (
-            "typescript",
-            tree_sitter_typescript::language_typescript(),
-            vec![
-                adapter_arc(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
-                adapter_arc(typescript::EsLintLspAdapter::new(node_runtime.clone())),
-            ],
-        ),
-        (
-            "javascript",
-            tree_sitter_typescript::language_tsx(),
-            vec![
-                adapter_arc(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
-                adapter_arc(typescript::EsLintLspAdapter::new(node_runtime.clone())),
-            ],
-        ),
-        (
-            "html",
-            tree_sitter_html::language(),
-            vec![adapter_arc(html::HtmlLspAdapter::new(node_runtime.clone()))],
-        ),
-        (
-            "ruby",
-            tree_sitter_ruby::language(),
-            vec![adapter_arc(ruby::RubyLanguageServer)],
-        ),
-        (
-            "erb",
-            tree_sitter_embedded_template::language(),
-            vec![adapter_arc(ruby::RubyLanguageServer)],
-        ),
-        ("scheme", tree_sitter_scheme::language(), vec![]),
-        ("racket", tree_sitter_racket::language(), vec![]),
-        (
-            "lua",
-            tree_sitter_lua::language(),
-            vec![adapter_arc(lua::LuaLspAdapter)],
-        ),
-        (
-            "yaml",
-            tree_sitter_yaml::language(),
-            vec![adapter_arc(yaml::YamlLspAdapter::new(node_runtime))],
-        ),
-    ];
+    let language = |name, grammar, adapters| {
+        languages.register(name, load_config(name), grammar, adapters, load_queries)
+    };
 
-    for (name, grammar, lsp_adapters) in languages_list {
-        languages.register(name, load_config(name), grammar, lsp_adapters, load_queries);
-    }
+    language(
+        "c",
+        tree_sitter_c::language(),
+        vec![Arc::new(c::CLspAdapter) as Arc<dyn LspAdapter>],
+    );
+    language(
+        "cpp",
+        tree_sitter_cpp::language(),
+        vec![Arc::new(c::CLspAdapter)],
+    );
+    language("css", tree_sitter_css::language(), vec![]);
+    language(
+        "elixir",
+        tree_sitter_elixir::language(),
+        vec![Arc::new(elixir::ElixirLspAdapter)],
+    );
+    language(
+        "go",
+        tree_sitter_go::language(),
+        vec![Arc::new(go::GoLspAdapter)],
+    );
+    language(
+        "heex",
+        tree_sitter_heex::language(),
+        vec![Arc::new(elixir::ElixirLspAdapter)],
+    );
+    language(
+        "json",
+        tree_sitter_json::language(),
+        vec![Arc::new(json::JsonLspAdapter::new(
+            node_runtime.clone(),
+            languages.clone(),
+        ))],
+    );
+    language("markdown", tree_sitter_markdown::language(), vec![]);
+    language(
+        "python",
+        tree_sitter_python::language(),
+        vec![Arc::new(python::PythonLspAdapter::new(
+            node_runtime.clone(),
+        ))],
+    );
+    language(
+        "rust",
+        tree_sitter_rust::language(),
+        vec![Arc::new(rust::RustLspAdapter)],
+    );
+    language("toml", tree_sitter_toml::language(), vec![]);
+    language(
+        "tsx",
+        tree_sitter_typescript::language_tsx(),
+        vec![
+            Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
+            Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())),
+        ],
+    );
+    language(
+        "typescript",
+        tree_sitter_typescript::language_typescript(),
+        vec![
+            Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
+            Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())),
+        ],
+    );
+    language(
+        "javascript",
+        tree_sitter_typescript::language_tsx(),
+        vec![
+            Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
+            Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())),
+        ],
+    );
+    language(
+        "html",
+        tree_sitter_html::language(),
+        vec![Arc::new(html::HtmlLspAdapter::new(node_runtime.clone()))],
+    );
+    language(
+        "ruby",
+        tree_sitter_ruby::language(),
+        vec![Arc::new(ruby::RubyLanguageServer)],
+    );
+    language(
+        "erb",
+        tree_sitter_embedded_template::language(),
+        vec![Arc::new(ruby::RubyLanguageServer)],
+    );
+    language("scheme", tree_sitter_scheme::language(), vec![]);
+    language("racket", tree_sitter_racket::language(), vec![]);
+    language(
+        "lua",
+        tree_sitter_lua::language(),
+        vec![Arc::new(lua::LuaLspAdapter)],
+    );
+    language(
+        "yaml",
+        tree_sitter_yaml::language(),
+        vec![Arc::new(yaml::YamlLspAdapter::new(node_runtime))],
+    );
 }
 
 #[cfg(any(test, feature = "test-support"))]

crates/zed/src/languages/heex/config.toml 🔗

@@ -0,0 +1,7 @@
+name = "HEEX"
+path_suffixes = ["heex"]
+autoclose_before = ">})"
+brackets = [
+    { start = "<", end = ">", close = true, newline = true },
+]
+block_comment = ["<%#", "%>"]

crates/zed/src/languages/heex/highlights.scm 🔗

@@ -0,0 +1,54 @@
+; HEEx delimiters
+[
+  "%>"
+  "--%>"
+  "-->"
+  "/>"
+  "<!"
+  "<!--"
+  "<"
+  "<%!--"
+  "<%"
+  "<%#"
+  "<%%="
+  "<%="
+  "</"
+  "</:"
+  "<:"
+  ">"
+  "{"
+  "}"
+] @punctuation.bracket
+
+; HEEx operators are highlighted as such
+"=" @operator
+
+; HEEx inherits the DOCTYPE tag from HTML
+(doctype) @constant
+
+(comment) @comment
+
+; HEEx tags and slots are highlighted as HTML
+[
+ (tag_name)
+ (slot_name)
+] @tag
+
+; HEEx attributes are highlighted as HTML attributes
+(attribute_name) @attribute
+
+; HEEx special attributes are highlighted as keywords
+(special_attribute_name) @keyword
+
+[
+  (attribute_value)
+  (quoted_attribute_value)
+] @string
+
+; HEEx components are highlighted as Elixir modules and functions
+(component_name
+  [
+    (module) @module
+    (function) @function
+    "." @punctuation.delimiter
+  ])

crates/zed/src/languages/heex/injections.scm 🔗

@@ -0,0 +1,13 @@
+((directive (partial_expression_value) @content)
+ (#set! language "elixir")
+ (#set! include-children)
+ (#set! combined))
+
+; Regular expression_values do not need to be combined
+((directive (expression_value) @content)
+ (#set! language "elixir"))
+
+; expressions live within HTML tags, and do not need to be combined
+;     <link href={ Routes.static_path(..) } />
+((expression (expression_value) @content)
+ (#set! language "elixir"))