Add support for Elm and GLSL (#2782)

Mikayla Maki created

This adds tree-sitter grammars for both Elm and GLSL, with injections
for GLSL embedded within Elm. It also adds an `outline.scm` for Elm,
though limitations in the tree-sitter grammar meant that I wasn't able
to get it looking exactly how I'd have liked.

In particular, it wasn't clear how to nicely annotate functions in the
outline as being functions, or how to prevent the fields of a record
declaration from being increasingly indented.


![image](https://github.com/zed-industries/zed/assets/285821/544bc00b-3bfc-4ec7-be9d-764b9f0292ab)

![image](https://github.com/zed-industries/zed/assets/285821/74e57e95-bf87-4989-ae29-a2f625141bcf)

![image](https://github.com/zed-industries/zed/assets/285821/9e283c78-66d5-4c15-9827-1b5b446cdc37)

fixes https://github.com/zed-industries/community/issues/598

Release Notes:
- Added syntax highlighting for the Elm and GLSL languages

Change summary

Cargo.lock                                   |  21 +++
Cargo.toml                                   |   2 
crates/zed/Cargo.toml                        |   2 
crates/zed/src/languages.rs                  |   2 
crates/zed/src/languages/elm/config.toml     |  11 ++
crates/zed/src/languages/elm/highlights.scm  |  72 +++++++++++++
crates/zed/src/languages/elm/injections.scm  |   2 
crates/zed/src/languages/elm/outline.scm     |  22 ++++
crates/zed/src/languages/glsl/config.toml    |   9 +
crates/zed/src/languages/glsl/highlights.scm | 118 ++++++++++++++++++++++
10 files changed, 261 insertions(+)

Detailed changes

Cargo.lock 🔗

@@ -7989,6 +7989,16 @@ dependencies = [
  "tree-sitter",
 ]
 
+[[package]]
+name = "tree-sitter-elm"
+version = "5.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec83a2e1cfc69d03c8e73636e95662d6c6728539538d341b21251a77039fb94e"
+dependencies = [
+ "cc",
+ "tree-sitter",
+]
+
 [[package]]
 name = "tree-sitter-embedded-template"
 version = "0.20.0"
@@ -7999,6 +8009,15 @@ dependencies = [
  "tree-sitter",
 ]
 
+[[package]]
+name = "tree-sitter-glsl"
+version = "0.1.4"
+source = "git+https://github.com/theHamsta/tree-sitter-glsl?rev=2a56fb7bc8bb03a1892b4741279dd0a8758b7fb3#2a56fb7bc8bb03a1892b4741279dd0a8758b7fb3"
+dependencies = [
+ "cc",
+ "tree-sitter",
+]
+
 [[package]]
 name = "tree-sitter-go"
 version = "0.19.1"
@@ -9543,7 +9562,9 @@ dependencies = [
  "tree-sitter-cpp",
  "tree-sitter-css",
  "tree-sitter-elixir",
+ "tree-sitter-elm",
  "tree-sitter-embedded-template",
+ "tree-sitter-glsl",
  "tree-sitter-go",
  "tree-sitter-heex",
  "tree-sitter-html",

Cargo.toml 🔗

@@ -112,7 +112,9 @@ tree-sitter-c = "0.20.1"
 tree-sitter-cpp = "0.20.0"
 tree-sitter-css = { git = "https://github.com/tree-sitter/tree-sitter-css", rev = "769203d0f9abe1a9a691ac2b9fe4bb4397a73c51" }
 tree-sitter-elixir = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "4ba9dab6e2602960d95b2b625f3386c27e08084e" }
+tree-sitter-elm = "5.6.4"
 tree-sitter-embedded-template = "0.20.0"
+tree-sitter-glsl = { git = "https://github.com/theHamsta/tree-sitter-glsl", rev = "2a56fb7bc8bb03a1892b4741279dd0a8758b7fb3" }
 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" }

crates/zed/Cargo.toml 🔗

@@ -109,7 +109,9 @@ tree-sitter-c.workspace = true
 tree-sitter-cpp.workspace = true
 tree-sitter-css.workspace = true
 tree-sitter-elixir.workspace = true
+tree-sitter-elm.workspace = true
 tree-sitter-embedded-template.workspace = true
+tree-sitter-glsl.workspace = true
 tree-sitter-go.workspace = true
 tree-sitter-heex.workspace = true
 tree-sitter-json.workspace = true

crates/zed/src/languages.rs 🔗

@@ -152,6 +152,8 @@ pub fn init(languages: Arc<LanguageRegistry>, node_runtime: Arc<NodeRuntime>) {
         tree_sitter_php::language(),
         vec![Arc::new(php::IntelephenseLspAdapter::new(node_runtime))],
     );
+    language("elm", tree_sitter_elm::language(), vec![]);
+    language("glsl", tree_sitter_glsl::language(), vec![]);
 }
 
 #[cfg(any(test, feature = "test-support"))]

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

@@ -0,0 +1,11 @@
+name = "Elm"
+path_suffixes = ["elm"]
+line_comment = "-- "
+block_comment = ["{- ", " -}"]
+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, not_in = ["string"] },
+    { start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] },
+]

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

@@ -0,0 +1,72 @@
+[
+    "if"
+    "then"
+    "else"
+    "let"
+    "in"
+    (case)
+    (of)
+    (backslash)
+    (as)
+    (port)
+    (exposing)
+    (alias)
+    (import)
+    (module)
+    (type)
+    (arrow)
+ ] @keyword
+
+[
+    (eq)
+    (operator_identifier)
+    (colon)
+] @operator
+
+(type_annotation(lower_case_identifier) @function)
+(port_annotation(lower_case_identifier) @function)
+(function_declaration_left(lower_case_identifier) @function.definition)
+
+(function_call_expr
+    target: (value_expr
+        name: (value_qid (lower_case_identifier) @function)))
+
+(exposed_value(lower_case_identifier) @function)
+(exposed_type(upper_case_identifier) @type)
+
+(field_access_expr(value_expr(value_qid)) @identifier)
+(lower_pattern) @variable
+(record_base_identifier) @identifier
+
+[
+    "("
+    ")"
+] @punctuation.bracket
+
+[
+    "|"
+    ","
+] @punctuation.delimiter
+
+(number_constant_expr) @constant
+
+(type_declaration(upper_case_identifier) @type)
+(type_ref) @type
+(type_alias_declaration name: (upper_case_identifier) @type)
+
+(value_expr(upper_case_qid(upper_case_identifier)) @type)
+
+[
+    (line_comment)
+    (block_comment)
+] @comment
+
+(string_escape) @string.escape
+
+[
+    (open_quote)
+    (close_quote)
+    (regular_string_part)
+    (open_char)
+    (close_char)
+] @string

crates/zed/src/languages/elm/outline.scm 🔗

@@ -0,0 +1,22 @@
+(type_declaration
+    (type) @context
+    (upper_case_identifier) @name) @item
+
+(type_alias_declaration
+    (type) @context
+    (alias) @context
+    name: (upper_case_identifier) @name) @item
+
+(type_alias_declaration
+    typeExpression:
+        (type_expression
+            part: (record_type
+                (field_type
+                    name: (lower_case_identifier) @name) @item)))
+
+(union_variant
+    name: (upper_case_identifier) @name) @item
+
+(value_declaration
+    functionDeclarationLeft:
+        (function_declaration_left(lower_case_identifier) @name)) @item

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

@@ -0,0 +1,9 @@
+name = "GLSL"
+path_suffixes = ["vert", "frag", "tesc", "tese", "geom", "comp"]
+line_comment = "// "
+block_comment = ["/* ", " */"]
+brackets = [
+    { start = "{", end = "}", close = true, newline = true },
+    { start = "[", end = "]", close = true, newline = true },
+    { start = "(", end = ")", close = true, newline = true },
+]

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

@@ -0,0 +1,118 @@
+"break" @keyword
+"case" @keyword
+"const" @keyword
+"continue" @keyword
+"default" @keyword
+"do" @keyword
+"else" @keyword
+"enum" @keyword
+"extern" @keyword
+"for" @keyword
+"if" @keyword
+"inline" @keyword
+"return" @keyword
+"sizeof" @keyword
+"static" @keyword
+"struct" @keyword
+"switch" @keyword
+"typedef" @keyword
+"union" @keyword
+"volatile" @keyword
+"while" @keyword
+
+"#define" @keyword
+"#elif" @keyword
+"#else" @keyword
+"#endif" @keyword
+"#if" @keyword
+"#ifdef" @keyword
+"#ifndef" @keyword
+"#include" @keyword
+(preproc_directive) @keyword
+
+"--" @operator
+"-" @operator
+"-=" @operator
+"->" @operator
+"=" @operator
+"!=" @operator
+"*" @operator
+"&" @operator
+"&&" @operator
+"+" @operator
+"++" @operator
+"+=" @operator
+"<" @operator
+"==" @operator
+">" @operator
+"||" @operator
+
+"." @delimiter
+";" @delimiter
+
+(string_literal) @string
+(system_lib_string) @string
+
+(null) @constant
+(number_literal) @number
+(char_literal) @number
+
+(call_expression
+  function: (identifier) @function)
+(call_expression
+  function: (field_expression
+    field: (field_identifier) @function))
+(function_declarator
+  declarator: (identifier) @function)
+(preproc_function_def
+  name: (identifier) @function.special)
+
+(field_identifier) @property
+(statement_identifier) @label
+(type_identifier) @type
+(primitive_type) @type
+(sized_type_specifier) @type
+
+((identifier) @constant
+ (#match? @constant "^[A-Z][A-Z\\d_]*$"))
+
+(identifier) @variable
+
+(comment) @comment
+; inherits: c
+
+[
+  "in"
+  "out"
+  "inout"
+  "uniform"
+  "shared"
+  "layout"
+  "attribute"
+  "varying"
+  "buffer"
+  "coherent"
+  "readonly"
+  "writeonly"
+  "precision"
+  "highp"
+  "mediump"
+  "lowp"
+  "centroid"
+  "sample"
+  "patch"
+  "smooth"
+  "flat"
+  "noperspective"
+  "invariant"
+  "precise"
+] @type.qualifier
+
+"subroutine" @keyword.function
+
+(extension_storage_class) @storageclass
+
+(
+  (identifier) @variable.builtin
+  (#match? @variable.builtin "^gl_")
+)