Add support for HEEx templates in Elixir, fix a bug in handling nested language injections (#2603)

Closes https://linear.app/zed-industries/issue/Z-2211/heex-support

Release Notes:

- Added support for Elixir HEEx templates.
- Fixed a bug that caused incorrect syntax highlighting in ERB templates
([#1619](https://github.com/zed-industries/community/issues/1619)).
This commit is contained in:
Max Brunsfeld 2023-06-12 17:49:07 -07:00 committed by GitHub
commit d8a2e176e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 1409 additions and 1278 deletions

10
Cargo.lock generated
View File

@ -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",

View File

@ -72,6 +72,8 @@ ctor.workspace = true
env_logger.workspace = true
indoc.workspace = true
rand.workspace = true
unindent.workspace = true
tree-sitter-embedded-template = "*"
tree-sitter-html = "*"
tree-sitter-javascript = "*"
@ -81,4 +83,3 @@ tree-sitter-rust = "*"
tree-sitter-python = "*"
tree-sitter-typescript = "*"
tree-sitter-ruby = "*"
unindent.workspace = true

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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" }

View File

@ -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 language = |name, grammar, adapters| {
languages.register(name, load_config(name), grammar, adapters, load_queries)
};
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))],
),
];
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"))]

View File

@ -0,0 +1,7 @@
; Phoenix HTML template
((sigil
(sigil_name) @_sigil_name
(quoted_content) @content)
(#eq? @_sigil_name "H")
(#set! language "heex"))

View File

@ -9,4 +9,4 @@
"%>"
"-%>"
"_%>"
] @keyword
] @keyword

View File

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

View File

@ -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
])

View File

@ -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"))