Repository: helix-editor/helix
Pull request #13260: Basic language support for Fennel.  Might even work.
Author: login: icefoxen
Created: 2025-04-03T16:49:18Z | Merged: 2025-04-15T23:05:44Z

Closes https://github.com/helix-editor/helix/issues/3846 .  After pondering it (for two years) it seems obvious that having a single grammar that tries to cover multiple Lisps is silly.

There are two different tree-sitter parsers for Fennel mentioned in that issue:

 * https://github.com/travonted/tree-sitter-fennel
 * https://github.com/alexmozaidze/tree-sitter-fennel

This PR has mostly-arbitrarily chosen the second one.  There's also several different `highlights.scm` options, I've mostly chosen the most recent one but tried to incorporate bits and pieces from the others where I could find them.


Diff:
diff --git a/book/src/generated/lang-support.md b/book/src/generated/lang-support.md
index f6aee3fe3410..cb31de4bc699 100644
--- a/book/src/generated/lang-support.md
+++ b/book/src/generated/lang-support.md
@@ -57,6 +57,7 @@
 | erb | ✓ |  |  |  |
 | erlang | ✓ | ✓ |  | `erlang_ls`, `elp` |
 | esdl | ✓ |  |  |  |
+| fennel | ✓ |  |  | `fennel-ls` |
 | fga | ✓ | ✓ | ✓ |  |
 | fidl | ✓ |  |  |  |
 | fish | ✓ | ✓ | ✓ | `fish-lsp` |
diff --git a/languages.toml b/languages.toml
index cf6471deaae3..062fc714fd3f 100644
--- a/languages.toml
+++ b/languages.toml
@@ -39,6 +39,7 @@ elm-language-server = { command = "elm-language-server" }
 elp = { command = "elp", args = ["server"] }
 elvish = { command = "elvish", args = ["-lsp"] }
 erlang-ls = { command = "erlang_ls" }
+fennel-ls = { command = "fennel-ls" }
 fish-lsp = { command = "fish-lsp", args = ["start"], environment = { fish_lsp_show_client_popups = "false" } }
 forc = { command = "forc", args = ["lsp"] }
 forth-lsp = { command = "forth-lsp" }
@@ -396,6 +397,20 @@ indent = { tab-width = 2, unit = "  " }
 name = "elixir"
 source = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "02a6f7fd4be28dd94ee4dd2ca19cb777053ea74e" }
 
+[[language]]
+name = "fennel"
+scope = "source.fennel"
+file-types = ["fnl", "fnlm"]
+shebangs = ["fennel"]
+comment-token = ";"
+language-servers = ["fennel-ls"]
+formatter = { command = "fnlfmt", args = ["-"]}
+indent = { tab-width = 2, unit = "  " }
+
+[[grammar]]
+name = "fennel"
+source = { git = "https://github.com/alexmozaidze/tree-sitter-fennel", rev = "cfbfa478dc2dbef267ee94ae4323d9c886f45e94" }
+
 [[language]]
 name = "fish"
 scope = "source.fish"
diff --git a/runtime/queries/fennel/highlights.scm b/runtime/queries/fennel/highlights.scm
new file mode 100644
index 000000000000..fe90279ae11e
--- /dev/null
+++ b/runtime/queries/fennel/highlights.scm
@@ -0,0 +1,193 @@
+; Most primitive nodes
+(shebang) @keyword.directive
+
+(comment) @comment
+
+(fn_form
+  name: [
+    (symbol) @function
+    (multi_symbol
+      member: (symbol_fragment) @function .)
+  ])
+
+(lambda_form
+  name: [
+    (symbol) @function
+    (multi_symbol
+      member: (symbol_fragment) @function .)
+  ])
+
+((symbol) @operator
+  (#any-of? @operator
+    ; arithmetic
+    "+" "-" "*" "/" "//" "%" "^"
+    ; comparison
+    ">" "<" ">=" "<=" "=" "~="
+    ; other
+    "#" "." "?." ".."))
+
+((symbol) @keyword.operator
+  (#any-of? @keyword.operator
+    ; comparison
+    "not="
+    ; boolean
+    "and" "or" "not"
+    ; bitwise
+    "lshift" "rshift" "band" "bor" "bxor" "bnot"
+    ; other
+    "length"))
+
+(case_guard
+  call: (_) @keyword.control.conditional)
+
+(case_guard_or_special
+  call: (_) @keyword.control.conditional)
+
+(case_catch
+  call: (symbol) @keyword)
+
+(import_macros_form
+  imports: (table_binding
+    (table_binding_pair
+      value: (symbol_binding) @function.macro)))
+
+
+((symbol) @keyword.function
+  (#any-of? @keyword.function "fn" "lambda" "λ" "hashfn"))
+
+((symbol) @keyword.control.repeat
+  (#any-of? @keyword.control.repeat "for" "each" "while"))
+
+((symbol) @keyword.control.conditional
+  (#any-of? @keyword.control.conditional "if" "when" "match" "case" "match-try" "case-try"))
+
+((symbol) @keyword
+  (#any-of? @keyword
+    "global" "local" "let" "set" "var" "comment" "do" "doc" "eval-compiler" "lua" "macros" "unquote"
+    "quote" "tset" "values" "tail!"))
+
+((symbol) @keyword.control.import
+  (#any-of? @keyword.control.import "require" "require-macros" "import-macros" "include"))
+
+((symbol) @function.macro
+  (#any-of? @function.macro
+    "collect" "icollect" "fcollect" "accumulate" "faccumulate" "->" "->>" "-?>" "-?>>" "?." "doto"
+    "macro" "macrodebug" "partial" "pick-args" "pick-values" "with-open"))
+
+((symbol) @variable.builtin
+  (#eq? @variable.builtin "..."))
+
+((symbol) @constant.builtin
+  (#eq? @constant.builtin "_VERSION"))
+
+((symbol) @function.builtin
+  (#any-of? @function.builtin
+    "assert" "collectgarbage" "dofile" "error" "getmetatable" "ipairs" "load" "loadfile" "next"
+    "pairs" "pcall" "print" "rawequal" "rawget" "rawlen" "rawset" "require" "select" "setmetatable"
+    "tonumber" "tostring" "type" "warn" "xpcall" "module" "setfenv" "loadstring" "unpack"))
+
+; TODO: Highlight builtin methods (`table.unpack`, etc) as @function.builtin
+([
+  (symbol) @variable.builtin
+  (multi_symbol
+    base: (symbol_fragment) @variable.builtin)
+]
+  (#any-of? @variable.builtin
+    "vim" "_G" "_ENV" "debug" "io" "jit" "math" "os" "package" "string" "table" "utf8"))
+
+([
+  (symbol) @variable.builtin
+  (multi_symbol
+    .
+    (symbol_fragment) @variable.builtin)
+]
+  (#eq? @variable.builtin "arg"))
+(symbol_option) @keyword.directive
+
+(escape_sequence) @constant.character.escape
+
+(multi_symbol
+  "." @punctuation.delimiter
+  member: (symbol_fragment) @variable.other.member)
+
+(list
+  call: (symbol) @function)
+
+(list
+  call: (multi_symbol
+    member: (symbol_fragment) @function .))
+
+(multi_symbol_method
+  ":" @punctuation.delimiter
+  method: (symbol_fragment) @function.method)
+
+(quasi_quote_reader_macro
+  macro: _ @punctuation.special)
+
+(quote_reader_macro
+  macro: _ @punctuation.special)
+
+(unquote_reader_macro
+  macro: _ @punctuation.special)
+
+(hashfn_reader_macro
+  macro: _ @keyword.function)
+
+(docstring) @comment.block.documentation
+
+; NOTE: The macro name is highlighted as @variable because it
+; gives a nicer contrast instead of everything being the same
+; color. Rust queries use this workaround too for `macro_rules!`.
+(macro_form
+  name: [
+    (symbol) @variable
+    (multi_symbol
+      member: (symbol_fragment) @variable .)
+  ])
+
+[
+  "("
+  ")"
+  "{"
+  "}"
+  "["
+  "]"
+] @punctuation.bracket
+
+(sequence_arguments
+  (symbol_binding) @variable.parameter)
+
+(sequence_arguments
+  (rest_binding
+    rhs: (symbol_binding) @variable.parameter))
+
+((symbol) @variable.parameter
+  (#any-of? @variable.parameter "$" "$..."))
+
+((symbol) @variable.parameter
+  (#any-of? @variable.parameter "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9"))
+
+[
+  (nil)
+  (nil_binding)
+] @constant.builtin
+
+[
+  (boolean)
+  (boolean_binding)
+] @constant.builtin.boolean
+
+[
+  (number)
+  (number_binding)
+] @constant.numeric
+
+[
+  (string)
+  (string_binding)
+] @string
+
+[
+  (symbol)
+  (symbol_binding)
+] @variable


Reviews:
user: login: uncenter
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T17:33:02Z
user: login: icefoxen
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T20:23:49Z
user: login: the-mikedavis
body: You can run `cargo xtask docgen` and commit the changes to fix the docs CI.

> Might even work.

I realize this statement is probably meant as a joke but you have tested this patch, right? You can use `cargo run` to run a debug build which will use these queries.
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-06T18:05:42Z
user: login: uncenter
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T18:26:18Z
user: login: the-mikedavis
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-15T23:05:21Z

Review comments:
diff_hunk: @@ -0,0 +1,196 @@
+; Most primitive nodes
+(shebang) @keyword.directive
+
+(comment) @comment @spell
+
+(fn_form
+  name: [
+    (symbol) @function
+    (multi_symbol
+      member: (symbol_fragment) @function .)
+  ])
+
+(lambda_form
+  name: [
+    (symbol) @function
+    (multi_symbol
+      member: (symbol_fragment) @function .)
+  ])
+
+((symbol) @operator
+  (#any-of? @operator
+    ; arithmetic
+    "+" "-" "*" "/" "//" "%" "^"
+    ; comparison
+    ">" "<" ">=" "<=" "=" "~="
+    ; other
+    "#" "." "?." ".."))
+
+((symbol) @keyword.operator
+  (#any-of? @keyword.operator
+    ; comparison
+    "not="
+    ; boolean
+    "and" "or" "not"
+    ; bitwise
+    "lshift" "rshift" "band" "bor" "bxor" "bnot"
+    ; other
+    "length"))
+
+(case_guard
+  call: (_) @keyword.conditional)
+
+(case_guard_or_special
+  call: (_) @keyword.conditional)
path: runtime/queries/fennel/highlights.scm
user: login: uncenter
body: ```suggestion
(case_guard
  call: (_) @keyword.control.conditional)

(case_guard_or_special
  call: (_) @keyword.control.conditional)
```
created_at: 2025-04-03T17:19:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,196 @@
+; Most primitive nodes
+(shebang) @keyword.directive
+
+(comment) @comment @spell
+
+(fn_form
+  name: [
+    (symbol) @function
+    (multi_symbol
+      member: (symbol_fragment) @function .)
+  ])
+
+(lambda_form
+  name: [
+    (symbol) @function
+    (multi_symbol
+      member: (symbol_fragment) @function .)
+  ])
+
+((symbol) @operator
+  (#any-of? @operator
+    ; arithmetic
+    "+" "-" "*" "/" "//" "%" "^"
+    ; comparison
+    ">" "<" ">=" "<=" "=" "~="
+    ; other
+    "#" "." "?." ".."))
+
+((symbol) @keyword.operator
+  (#any-of? @keyword.operator
+    ; comparison
+    "not="
+    ; boolean
+    "and" "or" "not"
+    ; bitwise
+    "lshift" "rshift" "band" "bor" "bxor" "bnot"
+    ; other
+    "length"))
+
+(case_guard
+  call: (_) @keyword.conditional)
+
+(case_guard_or_special
+  call: (_) @keyword.conditional)
+
+(case_catch
+  call: (symbol) @keyword)
+
+(import_macros_form
+  imports: (table_binding
+    (table_binding_pair
+      value: (symbol_binding) @function.macro)))
+
+
+((symbol) @keyword.function
+  (#any-of? @keyword.function "fn" "lambda" "λ" "hashfn"))
+
+((symbol) @keyword.repeat
+  (#any-of? @keyword.repeat "for" "each" "while"))
+
+((symbol) @keyword.conditional
+  (#any-of? @keyword.conditional "if" "when" "match" "case" "match-try" "case-try"))
path: runtime/queries/fennel/highlights.scm
user: login: uncenter
body: ```suggestion
((symbol) @keyword.control.repeat
  (#any-of? @keyword.control.repeat "for" "each" "while"))

((symbol) @keyword.control.conditional
  (#any-of? @keyword.control.conditional "if" "when" "match" "case" "match-try" "case-try"))
```
created_at: 2025-04-03T17:20:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,196 @@
+; Most primitive nodes
+(shebang) @keyword.directive
+
+(comment) @comment @spell
+
+(fn_form
+  name: [
+    (symbol) @function
+    (multi_symbol
+      member: (symbol_fragment) @function .)
+  ])
+
+(lambda_form
+  name: [
+    (symbol) @function
+    (multi_symbol
+      member: (symbol_fragment) @function .)
+  ])
+
+((symbol) @operator
+  (#any-of? @operator
+    ; arithmetic
+    "+" "-" "*" "/" "//" "%" "^"
+    ; comparison
+    ">" "<" ">=" "<=" "=" "~="
+    ; other
+    "#" "." "?." ".."))
+
+((symbol) @keyword.operator
+  (#any-of? @keyword.operator
+    ; comparison
+    "not="
+    ; boolean
+    "and" "or" "not"
+    ; bitwise
+    "lshift" "rshift" "band" "bor" "bxor" "bnot"
+    ; other
+    "length"))
+
+(case_guard
+  call: (_) @keyword.conditional)
+
+(case_guard_or_special
+  call: (_) @keyword.conditional)
+
+(case_catch
+  call: (symbol) @keyword)
+
+(import_macros_form
+  imports: (table_binding
+    (table_binding_pair
+      value: (symbol_binding) @function.macro)))
+
+
+((symbol) @keyword.function
+  (#any-of? @keyword.function "fn" "lambda" "λ" "hashfn"))
+
+((symbol) @keyword.repeat
+  (#any-of? @keyword.repeat "for" "each" "while"))
+
+((symbol) @keyword.conditional
+  (#any-of? @keyword.conditional "if" "when" "match" "case" "match-try" "case-try"))
+
+((symbol) @keyword
+  (#any-of? @keyword
+    "global" "local" "let" "set" "var" "comment" "do" "doc" "eval-compiler" "lua" "macros" "unquote"
+    "quote" "tset" "values" "tail!"))
+
+((symbol) @keyword.import
+  (#any-of? @keyword.import "require" "require-macros" "import-macros" "include"))
path: runtime/queries/fennel/highlights.scm
user: login: uncenter
body: ```suggestion
((symbol) @keyword.control.import
  (#any-of? @keyword.control.import "require" "require-macros" "import-macros" "include"))
```
created_at: 2025-04-03T17:20:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,196 @@
+; Most primitive nodes
+(shebang) @keyword.directive
+
+(comment) @comment @spell
+
+(fn_form
+  name: [
+    (symbol) @function
+    (multi_symbol
+      member: (symbol_fragment) @function .)
+  ])
+
+(lambda_form
+  name: [
+    (symbol) @function
+    (multi_symbol
+      member: (symbol_fragment) @function .)
+  ])
+
+((symbol) @operator
+  (#any-of? @operator
+    ; arithmetic
+    "+" "-" "*" "/" "//" "%" "^"
+    ; comparison
+    ">" "<" ">=" "<=" "=" "~="
+    ; other
+    "#" "." "?." ".."))
+
+((symbol) @keyword.operator
+  (#any-of? @keyword.operator
+    ; comparison
+    "not="
+    ; boolean
+    "and" "or" "not"
+    ; bitwise
+    "lshift" "rshift" "band" "bor" "bxor" "bnot"
+    ; other
+    "length"))
+
+(case_guard
+  call: (_) @keyword.conditional)
+
+(case_guard_or_special
+  call: (_) @keyword.conditional)
+
+(case_catch
+  call: (symbol) @keyword)
+
+(import_macros_form
+  imports: (table_binding
+    (table_binding_pair
+      value: (symbol_binding) @function.macro)))
+
+
+((symbol) @keyword.function
+  (#any-of? @keyword.function "fn" "lambda" "λ" "hashfn"))
+
+((symbol) @keyword.repeat
+  (#any-of? @keyword.repeat "for" "each" "while"))
+
+((symbol) @keyword.conditional
+  (#any-of? @keyword.conditional "if" "when" "match" "case" "match-try" "case-try"))
+
+((symbol) @keyword
+  (#any-of? @keyword
+    "global" "local" "let" "set" "var" "comment" "do" "doc" "eval-compiler" "lua" "macros" "unquote"
+    "quote" "tset" "values" "tail!"))
+
+((symbol) @keyword.import
+  (#any-of? @keyword.import "require" "require-macros" "import-macros" "include"))
+
+((symbol) @function.macro
+  (#any-of? @function.macro
+    "collect" "icollect" "fcollect" "accumulate" "faccumulate" "->" "->>" "-?>" "-?>>" "?." "doto"
+    "macro" "macrodebug" "partial" "pick-args" "pick-values" "with-open"))
+
+((symbol) @variable.builtin
+  (#eq? @variable.builtin "..."))
+
+((symbol) @constant.builtin
+  (#eq? @constant.builtin "_VERSION"))
+
+((symbol) @function.builtin
+  (#any-of? @function.builtin
+    "assert" "collectgarbage" "dofile" "error" "getmetatable" "ipairs" "load" "loadfile" "next"
+    "pairs" "pcall" "print" "rawequal" "rawget" "rawlen" "rawset" "require" "select" "setmetatable"
+    "tonumber" "tostring" "type" "warn" "xpcall" "module" "setfenv" "loadstring" "unpack"))
+
+; TODO: Highlight builtin methods (`table.unpack`, etc) as @function.builtin
+([
+  (symbol) @module.builtin
+  (multi_symbol
+    base: (symbol_fragment) @module.builtin)
+]
+  (#any-of? @module.builtin
+    "vim" "_G" "_ENV" "debug" "io" "jit" "math" "os" "package" "string" "table" "utf8"))
path: runtime/queries/fennel/highlights.scm
user: login: uncenter
body: `@module.builtin` isn't a thing in Helix, the closest we have is `@namespace` instead of `@module` but no `@namespace.builtin`.
created_at: 2025-04-03T17:22:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,196 @@
+; Most primitive nodes
+(shebang) @keyword.directive
+
+(comment) @comment @spell
+
+(fn_form
+  name: [
+    (symbol) @function
+    (multi_symbol
+      member: (symbol_fragment) @function .)
+  ])
+
+(lambda_form
+  name: [
+    (symbol) @function
+    (multi_symbol
+      member: (symbol_fragment) @function .)
+  ])
+
+((symbol) @operator
+  (#any-of? @operator
+    ; arithmetic
+    "+" "-" "*" "/" "//" "%" "^"
+    ; comparison
+    ">" "<" ">=" "<=" "=" "~="
+    ; other
+    "#" "." "?." ".."))
+
+((symbol) @keyword.operator
+  (#any-of? @keyword.operator
+    ; comparison
+    "not="
+    ; boolean
+    "and" "or" "not"
+    ; bitwise
+    "lshift" "rshift" "band" "bor" "bxor" "bnot"
+    ; other
+    "length"))
+
+(case_guard
+  call: (_) @keyword.conditional)
+
+(case_guard_or_special
+  call: (_) @keyword.conditional)
+
+(case_catch
+  call: (symbol) @keyword)
+
+(import_macros_form
+  imports: (table_binding
+    (table_binding_pair
+      value: (symbol_binding) @function.macro)))
+
+
+((symbol) @keyword.function
+  (#any-of? @keyword.function "fn" "lambda" "λ" "hashfn"))
+
+((symbol) @keyword.repeat
+  (#any-of? @keyword.repeat "for" "each" "while"))
+
+((symbol) @keyword.conditional
+  (#any-of? @keyword.conditional "if" "when" "match" "case" "match-try" "case-try"))
+
+((symbol) @keyword
+  (#any-of? @keyword
+    "global" "local" "let" "set" "var" "comment" "do" "doc" "eval-compiler" "lua" "macros" "unquote"
+    "quote" "tset" "values" "tail!"))
+
+((symbol) @keyword.import
+  (#any-of? @keyword.import "require" "require-macros" "import-macros" "include"))
+
+((symbol) @function.macro
+  (#any-of? @function.macro
+    "collect" "icollect" "fcollect" "accumulate" "faccumulate" "->" "->>" "-?>" "-?>>" "?." "doto"
+    "macro" "macrodebug" "partial" "pick-args" "pick-values" "with-open"))
+
+((symbol) @variable.builtin
+  (#eq? @variable.builtin "..."))
+
+((symbol) @constant.builtin
+  (#eq? @constant.builtin "_VERSION"))
+
+((symbol) @function.builtin
+  (#any-of? @function.builtin
+    "assert" "collectgarbage" "dofile" "error" "getmetatable" "ipairs" "load" "loadfile" "next"
+    "pairs" "pcall" "print" "rawequal" "rawget" "rawlen" "rawset" "require" "select" "setmetatable"
+    "tonumber" "tostring" "type" "warn" "xpcall" "module" "setfenv" "loadstring" "unpack"))
+
+; TODO: Highlight builtin methods (`table.unpack`, etc) as @function.builtin
+([
+  (symbol) @module.builtin
+  (multi_symbol
+    base: (symbol_fragment) @module.builtin)
+]
+  (#any-of? @module.builtin
+    "vim" "_G" "_ENV" "debug" "io" "jit" "math" "os" "package" "string" "table" "utf8"))
+
+([
+  (symbol) @variable.builtin
+  (multi_symbol
+    .
+    (symbol_fragment) @variable.builtin)
+]
+  (#eq? @variable.builtin "arg"))
+(symbol_option) @keyword.directive
+
+(escape_sequence) @string.escape
+
+(multi_symbol
+  "." @punctuation.delimiter
+  member: (symbol_fragment) @variable.member)
path: runtime/queries/fennel/highlights.scm
user: login: uncenter
body: ```suggestion
  member: (symbol_fragment) @variable.other.member)
```
created_at: 2025-04-03T17:30:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,196 @@
+; Most primitive nodes
+(shebang) @keyword.directive
+
+(comment) @comment @spell
+
+(fn_form
+  name: [
+    (symbol) @function
+    (multi_symbol
+      member: (symbol_fragment) @function .)
+  ])
+
+(lambda_form
+  name: [
+    (symbol) @function
+    (multi_symbol
+      member: (symbol_fragment) @function .)
+  ])
+
+((symbol) @operator
+  (#any-of? @operator
+    ; arithmetic
+    "+" "-" "*" "/" "//" "%" "^"
+    ; comparison
+    ">" "<" ">=" "<=" "=" "~="
+    ; other
+    "#" "." "?." ".."))
+
+((symbol) @keyword.operator
+  (#any-of? @keyword.operator
+    ; comparison
+    "not="
+    ; boolean
+    "and" "or" "not"
+    ; bitwise
+    "lshift" "rshift" "band" "bor" "bxor" "bnot"
+    ; other
+    "length"))
+
+(case_guard
+  call: (_) @keyword.conditional)
+
+(case_guard_or_special
+  call: (_) @keyword.conditional)
+
+(case_catch
+  call: (symbol) @keyword)
+
+(import_macros_form
+  imports: (table_binding
+    (table_binding_pair
+      value: (symbol_binding) @function.macro)))
+
+
+((symbol) @keyword.function
+  (#any-of? @keyword.function "fn" "lambda" "λ" "hashfn"))
+
+((symbol) @keyword.repeat
+  (#any-of? @keyword.repeat "for" "each" "while"))
+
+((symbol) @keyword.conditional
+  (#any-of? @keyword.conditional "if" "when" "match" "case" "match-try" "case-try"))
+
+((symbol) @keyword
+  (#any-of? @keyword
+    "global" "local" "let" "set" "var" "comment" "do" "doc" "eval-compiler" "lua" "macros" "unquote"
+    "quote" "tset" "values" "tail!"))
+
+((symbol) @keyword.import
+  (#any-of? @keyword.import "require" "require-macros" "import-macros" "include"))
+
+((symbol) @function.macro
+  (#any-of? @function.macro
+    "collect" "icollect" "fcollect" "accumulate" "faccumulate" "->" "->>" "-?>" "-?>>" "?." "doto"
+    "macro" "macrodebug" "partial" "pick-args" "pick-values" "with-open"))
+
+((symbol) @variable.builtin
+  (#eq? @variable.builtin "..."))
+
+((symbol) @constant.builtin
+  (#eq? @constant.builtin "_VERSION"))
+
+((symbol) @function.builtin
+  (#any-of? @function.builtin
+    "assert" "collectgarbage" "dofile" "error" "getmetatable" "ipairs" "load" "loadfile" "next"
+    "pairs" "pcall" "print" "rawequal" "rawget" "rawlen" "rawset" "require" "select" "setmetatable"
+    "tonumber" "tostring" "type" "warn" "xpcall" "module" "setfenv" "loadstring" "unpack"))
+
+; TODO: Highlight builtin methods (`table.unpack`, etc) as @function.builtin
+([
+  (symbol) @module.builtin
+  (multi_symbol
+    base: (symbol_fragment) @module.builtin)
+]
+  (#any-of? @module.builtin
+    "vim" "_G" "_ENV" "debug" "io" "jit" "math" "os" "package" "string" "table" "utf8"))
+
+([
+  (symbol) @variable.builtin
+  (multi_symbol
+    .
+    (symbol_fragment) @variable.builtin)
+]
+  (#eq? @variable.builtin "arg"))
+(symbol_option) @keyword.directive
+
+(escape_sequence) @string.escape
path: runtime/queries/fennel/highlights.scm
user: login: uncenter
body: ```suggestion
(escape_sequence) @constant.character.escape
```
created_at: 2025-04-03T17:30:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,196 @@
+; Most primitive nodes
+(shebang) @keyword.directive
+
+(comment) @comment @spell
+
+(fn_form
+  name: [
+    (symbol) @function
+    (multi_symbol
+      member: (symbol_fragment) @function .)
+  ])
+
+(lambda_form
+  name: [
+    (symbol) @function
+    (multi_symbol
+      member: (symbol_fragment) @function .)
+  ])
+
+((symbol) @operator
+  (#any-of? @operator
+    ; arithmetic
+    "+" "-" "*" "/" "//" "%" "^"
+    ; comparison
+    ">" "<" ">=" "<=" "=" "~="
+    ; other
+    "#" "." "?." ".."))
+
+((symbol) @keyword.operator
+  (#any-of? @keyword.operator
+    ; comparison
+    "not="
+    ; boolean
+    "and" "or" "not"
+    ; bitwise
+    "lshift" "rshift" "band" "bor" "bxor" "bnot"
+    ; other
+    "length"))
+
+(case_guard
+  call: (_) @keyword.conditional)
+
+(case_guard_or_special
+  call: (_) @keyword.conditional)
+
+(case_catch
+  call: (symbol) @keyword)
+
+(import_macros_form
+  imports: (table_binding
+    (table_binding_pair
+      value: (symbol_binding) @function.macro)))
+
+
+((symbol) @keyword.function
+  (#any-of? @keyword.function "fn" "lambda" "λ" "hashfn"))
+
+((symbol) @keyword.repeat
+  (#any-of? @keyword.repeat "for" "each" "while"))
+
+((symbol) @keyword.conditional
+  (#any-of? @keyword.conditional "if" "when" "match" "case" "match-try" "case-try"))
+
+((symbol) @keyword
+  (#any-of? @keyword
+    "global" "local" "let" "set" "var" "comment" "do" "doc" "eval-compiler" "lua" "macros" "unquote"
+    "quote" "tset" "values" "tail!"))
+
+((symbol) @keyword.import
+  (#any-of? @keyword.import "require" "require-macros" "import-macros" "include"))
+
+((symbol) @function.macro
+  (#any-of? @function.macro
+    "collect" "icollect" "fcollect" "accumulate" "faccumulate" "->" "->>" "-?>" "-?>>" "?." "doto"
+    "macro" "macrodebug" "partial" "pick-args" "pick-values" "with-open"))
+
+((symbol) @variable.builtin
+  (#eq? @variable.builtin "..."))
+
+((symbol) @constant.builtin
+  (#eq? @constant.builtin "_VERSION"))
+
+((symbol) @function.builtin
+  (#any-of? @function.builtin
+    "assert" "collectgarbage" "dofile" "error" "getmetatable" "ipairs" "load" "loadfile" "next"
+    "pairs" "pcall" "print" "rawequal" "rawget" "rawlen" "rawset" "require" "select" "setmetatable"
+    "tonumber" "tostring" "type" "warn" "xpcall" "module" "setfenv" "loadstring" "unpack"))
+
+; TODO: Highlight builtin methods (`table.unpack`, etc) as @function.builtin
+([
+  (symbol) @module.builtin
+  (multi_symbol
+    base: (symbol_fragment) @module.builtin)
+]
+  (#any-of? @module.builtin
+    "vim" "_G" "_ENV" "debug" "io" "jit" "math" "os" "package" "string" "table" "utf8"))
+
+([
+  (symbol) @variable.builtin
+  (multi_symbol
+    .
+    (symbol_fragment) @variable.builtin)
+]
+  (#eq? @variable.builtin "arg"))
+(symbol_option) @keyword.directive
+
+(escape_sequence) @string.escape
+
+(multi_symbol
+  "." @punctuation.delimiter
+  member: (symbol_fragment) @variable.member)
+
+(list
+  call: (symbol) @function.call)
+
+(list
+  call: (multi_symbol
+    member: (symbol_fragment) @function.call .))
+
+(multi_symbol_method
+  ":" @punctuation.delimiter
+  method: (symbol_fragment) @function.method.call)
path: runtime/queries/fennel/highlights.scm
user: login: uncenter
body: ```suggestion
(list
  call: (symbol) @function)

(list
  call: (multi_symbol
    member: (symbol_fragment) @function .))

(multi_symbol_method
  ":" @punctuation.delimiter
  method: (symbol_fragment) @function.method)
```
created_at: 2025-04-03T17:31:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,196 @@
+; Most primitive nodes
+(shebang) @keyword.directive
+
+(comment) @comment @spell
+
+(fn_form
+  name: [
+    (symbol) @function
+    (multi_symbol
+      member: (symbol_fragment) @function .)
+  ])
+
+(lambda_form
+  name: [
+    (symbol) @function
+    (multi_symbol
+      member: (symbol_fragment) @function .)
+  ])
+
+((symbol) @operator
+  (#any-of? @operator
+    ; arithmetic
+    "+" "-" "*" "/" "//" "%" "^"
+    ; comparison
+    ">" "<" ">=" "<=" "=" "~="
+    ; other
+    "#" "." "?." ".."))
+
+((symbol) @keyword.operator
+  (#any-of? @keyword.operator
+    ; comparison
+    "not="
+    ; boolean
+    "and" "or" "not"
+    ; bitwise
+    "lshift" "rshift" "band" "bor" "bxor" "bnot"
+    ; other
+    "length"))
+
+(case_guard
+  call: (_) @keyword.conditional)
+
+(case_guard_or_special
+  call: (_) @keyword.conditional)
+
+(case_catch
+  call: (symbol) @keyword)
+
+(import_macros_form
+  imports: (table_binding
+    (table_binding_pair
+      value: (symbol_binding) @function.macro)))
+
+
+((symbol) @keyword.function
+  (#any-of? @keyword.function "fn" "lambda" "λ" "hashfn"))
+
+((symbol) @keyword.repeat
+  (#any-of? @keyword.repeat "for" "each" "while"))
+
+((symbol) @keyword.conditional
+  (#any-of? @keyword.conditional "if" "when" "match" "case" "match-try" "case-try"))
+
+((symbol) @keyword
+  (#any-of? @keyword
+    "global" "local" "let" "set" "var" "comment" "do" "doc" "eval-compiler" "lua" "macros" "unquote"
+    "quote" "tset" "values" "tail!"))
+
+((symbol) @keyword.import
+  (#any-of? @keyword.import "require" "require-macros" "import-macros" "include"))
+
+((symbol) @function.macro
+  (#any-of? @function.macro
+    "collect" "icollect" "fcollect" "accumulate" "faccumulate" "->" "->>" "-?>" "-?>>" "?." "doto"
+    "macro" "macrodebug" "partial" "pick-args" "pick-values" "with-open"))
+
+((symbol) @variable.builtin
+  (#eq? @variable.builtin "..."))
+
+((symbol) @constant.builtin
+  (#eq? @constant.builtin "_VERSION"))
+
+((symbol) @function.builtin
+  (#any-of? @function.builtin
+    "assert" "collectgarbage" "dofile" "error" "getmetatable" "ipairs" "load" "loadfile" "next"
+    "pairs" "pcall" "print" "rawequal" "rawget" "rawlen" "rawset" "require" "select" "setmetatable"
+    "tonumber" "tostring" "type" "warn" "xpcall" "module" "setfenv" "loadstring" "unpack"))
+
+; TODO: Highlight builtin methods (`table.unpack`, etc) as @function.builtin
+([
+  (symbol) @module.builtin
+  (multi_symbol
+    base: (symbol_fragment) @module.builtin)
+]
+  (#any-of? @module.builtin
+    "vim" "_G" "_ENV" "debug" "io" "jit" "math" "os" "package" "string" "table" "utf8"))
+
+([
+  (symbol) @variable.builtin
+  (multi_symbol
+    .
+    (symbol_fragment) @variable.builtin)
+]
+  (#eq? @variable.builtin "arg"))
+(symbol_option) @keyword.directive
+
+(escape_sequence) @string.escape
+
+(multi_symbol
+  "." @punctuation.delimiter
+  member: (symbol_fragment) @variable.member)
+
+(list
+  call: (symbol) @function.call)
+
+(list
+  call: (multi_symbol
+    member: (symbol_fragment) @function.call .))
+
+(multi_symbol_method
+  ":" @punctuation.delimiter
+  method: (symbol_fragment) @function.method.call)
+
+(quasi_quote_reader_macro
+  macro: _ @punctuation.special)
+
+(quote_reader_macro
+  macro: _ @punctuation.special)
+
+(unquote_reader_macro
+  macro: _ @punctuation.special)
+
+[ ":" ":until" "&" "&as" "?" ] @punctuation.special
+(vararg) @punctuation.special
+
+(hashfn_reader_macro
+  macro: _ @keyword.function)
+
+(docstring) @string.documentation
path: runtime/queries/fennel/highlights.scm
user: login: uncenter
body: ```suggestion
(docstring) @comment.block.documentation
```
I don't think Helix has a docstring scope, this seems like the closest but I'm not sure.
created_at: 2025-04-03T17:32:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,196 @@
+; Most primitive nodes
+(shebang) @keyword.directive
+
+(comment) @comment @spell
+
+(fn_form
+  name: [
+    (symbol) @function
+    (multi_symbol
+      member: (symbol_fragment) @function .)
+  ])
+
+(lambda_form
+  name: [
+    (symbol) @function
+    (multi_symbol
+      member: (symbol_fragment) @function .)
+  ])
+
+((symbol) @operator
+  (#any-of? @operator
+    ; arithmetic
+    "+" "-" "*" "/" "//" "%" "^"
+    ; comparison
+    ">" "<" ">=" "<=" "=" "~="
+    ; other
+    "#" "." "?." ".."))
+
+((symbol) @keyword.operator
+  (#any-of? @keyword.operator
+    ; comparison
+    "not="
+    ; boolean
+    "and" "or" "not"
+    ; bitwise
+    "lshift" "rshift" "band" "bor" "bxor" "bnot"
+    ; other
+    "length"))
+
+(case_guard
+  call: (_) @keyword.conditional)
+
+(case_guard_or_special
+  call: (_) @keyword.conditional)
+
+(case_catch
+  call: (symbol) @keyword)
+
+(import_macros_form
+  imports: (table_binding
+    (table_binding_pair
+      value: (symbol_binding) @function.macro)))
+
+
+((symbol) @keyword.function
+  (#any-of? @keyword.function "fn" "lambda" "λ" "hashfn"))
+
+((symbol) @keyword.repeat
+  (#any-of? @keyword.repeat "for" "each" "while"))
+
+((symbol) @keyword.conditional
+  (#any-of? @keyword.conditional "if" "when" "match" "case" "match-try" "case-try"))
+
+((symbol) @keyword
+  (#any-of? @keyword
+    "global" "local" "let" "set" "var" "comment" "do" "doc" "eval-compiler" "lua" "macros" "unquote"
+    "quote" "tset" "values" "tail!"))
+
+((symbol) @keyword.import
+  (#any-of? @keyword.import "require" "require-macros" "import-macros" "include"))
+
+((symbol) @function.macro
+  (#any-of? @function.macro
+    "collect" "icollect" "fcollect" "accumulate" "faccumulate" "->" "->>" "-?>" "-?>>" "?." "doto"
+    "macro" "macrodebug" "partial" "pick-args" "pick-values" "with-open"))
+
+((symbol) @variable.builtin
+  (#eq? @variable.builtin "..."))
+
+((symbol) @constant.builtin
+  (#eq? @constant.builtin "_VERSION"))
+
+((symbol) @function.builtin
+  (#any-of? @function.builtin
+    "assert" "collectgarbage" "dofile" "error" "getmetatable" "ipairs" "load" "loadfile" "next"
+    "pairs" "pcall" "print" "rawequal" "rawget" "rawlen" "rawset" "require" "select" "setmetatable"
+    "tonumber" "tostring" "type" "warn" "xpcall" "module" "setfenv" "loadstring" "unpack"))
+
+; TODO: Highlight builtin methods (`table.unpack`, etc) as @function.builtin
+([
+  (symbol) @module.builtin
+  (multi_symbol
+    base: (symbol_fragment) @module.builtin)
+]
+  (#any-of? @module.builtin
+    "vim" "_G" "_ENV" "debug" "io" "jit" "math" "os" "package" "string" "table" "utf8"))
+
+([
+  (symbol) @variable.builtin
+  (multi_symbol
+    .
+    (symbol_fragment) @variable.builtin)
+]
+  (#eq? @variable.builtin "arg"))
+(symbol_option) @keyword.directive
+
+(escape_sequence) @string.escape
+
+(multi_symbol
+  "." @punctuation.delimiter
+  member: (symbol_fragment) @variable.member)
+
+(list
+  call: (symbol) @function.call)
+
+(list
+  call: (multi_symbol
+    member: (symbol_fragment) @function.call .))
+
+(multi_symbol_method
+  ":" @punctuation.delimiter
+  method: (symbol_fragment) @function.method.call)
+
+(quasi_quote_reader_macro
+  macro: _ @punctuation.special)
+
+(quote_reader_macro
+  macro: _ @punctuation.special)
+
+(unquote_reader_macro
+  macro: _ @punctuation.special)
+
+[ ":" ":until" "&" "&as" "?" ] @punctuation.special
+(vararg) @punctuation.special
+
+(hashfn_reader_macro
+  macro: _ @keyword.function)
+
+(docstring) @string.documentation
+
+; NOTE: The macro name is highlighted as @variable because it
+; gives a nicer contrast instead of everything being the same
+; color. Rust queries use this workaround too for `macro_rules!`.
+(macro_form
+  name: [
+    (symbol) @variable
+    (multi_symbol
+      member: (symbol_fragment) @variable .)
+  ])
+
+[
+  "("
+  ")"
+  "{"
+  "}"
+  "["
+  "]"
+] @punctuation.bracket
+
+(sequence_arguments
+  (symbol_binding) @variable.parameter)
+
+(sequence_arguments
+  (rest_binding
+    rhs: (symbol_binding) @variable.parameter))
+
+((symbol) @variable.parameter
+  (#any-of? @variable.parameter "$" "$..."))
+
+((symbol) @variable.parameter
+  (#any-of? @variable.parameter "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9"))
+
+[
+  (nil)
+  (nil_binding)
+] @constant.builtin
+
+[
+  (boolean)
+  (boolean_binding)
+] @boolean
path: runtime/queries/fennel/highlights.scm
user: login: uncenter
body: ```suggestion
] @constant.builtin.boolean
```
created_at: 2025-04-03T17:32:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,196 @@
+; Most primitive nodes
+(shebang) @keyword.directive
+
+(comment) @comment @spell
+
+(fn_form
+  name: [
+    (symbol) @function
+    (multi_symbol
+      member: (symbol_fragment) @function .)
+  ])
+
+(lambda_form
+  name: [
+    (symbol) @function
+    (multi_symbol
+      member: (symbol_fragment) @function .)
+  ])
+
+((symbol) @operator
+  (#any-of? @operator
+    ; arithmetic
+    "+" "-" "*" "/" "//" "%" "^"
+    ; comparison
+    ">" "<" ">=" "<=" "=" "~="
+    ; other
+    "#" "." "?." ".."))
+
+((symbol) @keyword.operator
+  (#any-of? @keyword.operator
+    ; comparison
+    "not="
+    ; boolean
+    "and" "or" "not"
+    ; bitwise
+    "lshift" "rshift" "band" "bor" "bxor" "bnot"
+    ; other
+    "length"))
+
+(case_guard
+  call: (_) @keyword.conditional)
+
+(case_guard_or_special
+  call: (_) @keyword.conditional)
+
+(case_catch
+  call: (symbol) @keyword)
+
+(import_macros_form
+  imports: (table_binding
+    (table_binding_pair
+      value: (symbol_binding) @function.macro)))
+
+
+((symbol) @keyword.function
+  (#any-of? @keyword.function "fn" "lambda" "λ" "hashfn"))
+
+((symbol) @keyword.repeat
+  (#any-of? @keyword.repeat "for" "each" "while"))
+
+((symbol) @keyword.conditional
+  (#any-of? @keyword.conditional "if" "when" "match" "case" "match-try" "case-try"))
+
+((symbol) @keyword
+  (#any-of? @keyword
+    "global" "local" "let" "set" "var" "comment" "do" "doc" "eval-compiler" "lua" "macros" "unquote"
+    "quote" "tset" "values" "tail!"))
+
+((symbol) @keyword.import
+  (#any-of? @keyword.import "require" "require-macros" "import-macros" "include"))
+
+((symbol) @function.macro
+  (#any-of? @function.macro
+    "collect" "icollect" "fcollect" "accumulate" "faccumulate" "->" "->>" "-?>" "-?>>" "?." "doto"
+    "macro" "macrodebug" "partial" "pick-args" "pick-values" "with-open"))
+
+((symbol) @variable.builtin
+  (#eq? @variable.builtin "..."))
+
+((symbol) @constant.builtin
+  (#eq? @constant.builtin "_VERSION"))
+
+((symbol) @function.builtin
+  (#any-of? @function.builtin
+    "assert" "collectgarbage" "dofile" "error" "getmetatable" "ipairs" "load" "loadfile" "next"
+    "pairs" "pcall" "print" "rawequal" "rawget" "rawlen" "rawset" "require" "select" "setmetatable"
+    "tonumber" "tostring" "type" "warn" "xpcall" "module" "setfenv" "loadstring" "unpack"))
+
+; TODO: Highlight builtin methods (`table.unpack`, etc) as @function.builtin
+([
+  (symbol) @module.builtin
+  (multi_symbol
+    base: (symbol_fragment) @module.builtin)
+]
+  (#any-of? @module.builtin
+    "vim" "_G" "_ENV" "debug" "io" "jit" "math" "os" "package" "string" "table" "utf8"))
+
+([
+  (symbol) @variable.builtin
+  (multi_symbol
+    .
+    (symbol_fragment) @variable.builtin)
+]
+  (#eq? @variable.builtin "arg"))
+(symbol_option) @keyword.directive
+
+(escape_sequence) @string.escape
+
+(multi_symbol
+  "." @punctuation.delimiter
+  member: (symbol_fragment) @variable.member)
+
+(list
+  call: (symbol) @function.call)
+
+(list
+  call: (multi_symbol
+    member: (symbol_fragment) @function.call .))
+
+(multi_symbol_method
+  ":" @punctuation.delimiter
+  method: (symbol_fragment) @function.method.call)
+
+(quasi_quote_reader_macro
+  macro: _ @punctuation.special)
+
+(quote_reader_macro
+  macro: _ @punctuation.special)
+
+(unquote_reader_macro
+  macro: _ @punctuation.special)
+
+[ ":" ":until" "&" "&as" "?" ] @punctuation.special
+(vararg) @punctuation.special
+
+(hashfn_reader_macro
+  macro: _ @keyword.function)
+
+(docstring) @string.documentation
+
+; NOTE: The macro name is highlighted as @variable because it
+; gives a nicer contrast instead of everything being the same
+; color. Rust queries use this workaround too for `macro_rules!`.
+(macro_form
+  name: [
+    (symbol) @variable
+    (multi_symbol
+      member: (symbol_fragment) @variable .)
+  ])
+
+[
+  "("
+  ")"
+  "{"
+  "}"
+  "["
+  "]"
+] @punctuation.bracket
+
+(sequence_arguments
+  (symbol_binding) @variable.parameter)
+
+(sequence_arguments
+  (rest_binding
+    rhs: (symbol_binding) @variable.parameter))
+
+((symbol) @variable.parameter
+  (#any-of? @variable.parameter "$" "$..."))
+
+((symbol) @variable.parameter
+  (#any-of? @variable.parameter "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9"))
+
+[
+  (nil)
+  (nil_binding)
+] @constant.builtin
+
+[
+  (boolean)
+  (boolean_binding)
+] @boolean
+
+[
+  (number)
+  (number_binding)
+] @number
path: runtime/queries/fennel/highlights.scm
user: login: uncenter
body: ```suggestion
[
  (number)
  (number_binding)
] @constant.numeric
```
created_at: 2025-04-03T17:32:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,196 @@
+; Most primitive nodes
+(shebang) @keyword.directive
+
+(comment) @comment @spell
+
+(fn_form
+  name: [
+    (symbol) @function
+    (multi_symbol
+      member: (symbol_fragment) @function .)
+  ])
+
+(lambda_form
+  name: [
+    (symbol) @function
+    (multi_symbol
+      member: (symbol_fragment) @function .)
+  ])
+
+((symbol) @operator
+  (#any-of? @operator
+    ; arithmetic
+    "+" "-" "*" "/" "//" "%" "^"
+    ; comparison
+    ">" "<" ">=" "<=" "=" "~="
+    ; other
+    "#" "." "?." ".."))
+
+((symbol) @keyword.operator
+  (#any-of? @keyword.operator
+    ; comparison
+    "not="
+    ; boolean
+    "and" "or" "not"
+    ; bitwise
+    "lshift" "rshift" "band" "bor" "bxor" "bnot"
+    ; other
+    "length"))
+
+(case_guard
+  call: (_) @keyword.conditional)
+
+(case_guard_or_special
+  call: (_) @keyword.conditional)
+
+(case_catch
+  call: (symbol) @keyword)
+
+(import_macros_form
+  imports: (table_binding
+    (table_binding_pair
+      value: (symbol_binding) @function.macro)))
+
+
+((symbol) @keyword.function
+  (#any-of? @keyword.function "fn" "lambda" "λ" "hashfn"))
+
+((symbol) @keyword.repeat
+  (#any-of? @keyword.repeat "for" "each" "while"))
+
+((symbol) @keyword.conditional
+  (#any-of? @keyword.conditional "if" "when" "match" "case" "match-try" "case-try"))
+
+((symbol) @keyword
+  (#any-of? @keyword
+    "global" "local" "let" "set" "var" "comment" "do" "doc" "eval-compiler" "lua" "macros" "unquote"
+    "quote" "tset" "values" "tail!"))
+
+((symbol) @keyword.import
+  (#any-of? @keyword.import "require" "require-macros" "import-macros" "include"))
+
+((symbol) @function.macro
+  (#any-of? @function.macro
+    "collect" "icollect" "fcollect" "accumulate" "faccumulate" "->" "->>" "-?>" "-?>>" "?." "doto"
+    "macro" "macrodebug" "partial" "pick-args" "pick-values" "with-open"))
+
+((symbol) @variable.builtin
+  (#eq? @variable.builtin "..."))
+
+((symbol) @constant.builtin
+  (#eq? @constant.builtin "_VERSION"))
+
+((symbol) @function.builtin
+  (#any-of? @function.builtin
+    "assert" "collectgarbage" "dofile" "error" "getmetatable" "ipairs" "load" "loadfile" "next"
+    "pairs" "pcall" "print" "rawequal" "rawget" "rawlen" "rawset" "require" "select" "setmetatable"
+    "tonumber" "tostring" "type" "warn" "xpcall" "module" "setfenv" "loadstring" "unpack"))
+
+; TODO: Highlight builtin methods (`table.unpack`, etc) as @function.builtin
+([
+  (symbol) @module.builtin
+  (multi_symbol
+    base: (symbol_fragment) @module.builtin)
+]
+  (#any-of? @module.builtin
+    "vim" "_G" "_ENV" "debug" "io" "jit" "math" "os" "package" "string" "table" "utf8"))
path: runtime/queries/fennel/highlights.scm
user: login: icefoxen
body: Good catch, I've replaced it with `@variable.builtin` which is pretty close to what I mean anyway.  Thanks for unhecking my voodoo.
created_at: 2025-04-03T20:23:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,196 @@
+; Most primitive nodes
+(shebang) @keyword.directive
+
+(comment) @comment @spell
path: runtime/queries/fennel/highlights.scm
user: login: the-mikedavis
body: ```suggestion
(comment) @comment
```

Neovim includes spellchecking queries in the highlights. When we introduce tree-sitter based spellchecking we will keep the queries in a different file so we can remove all `@spell` captures
created_at: 2025-04-06T18:04:24Z
author_association: MEMBER
diff_hunk: @@ -396,6 +397,21 @@ indent = { tab-width = 2, unit = "  " }
 name = "elixir"
 source = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "02a6f7fd4be28dd94ee4dd2ca19cb777053ea74e" }
 
+[[language]]
+name = "fennel"
+scope = "source.fennel"
+file-types = ["fnl", "fnlm"]
+shebangs = ["fennel"]
+comment-token = ";"
+language-servers = ["fennel-ls"]
+formatter = { command = "fnlfmt", args = ["-"]}
+indent = { tab-width = 2, unit = "  " }
+
+[[grammar]]
+name = "fennel"
+source.git = "https://github.com/alexmozaidze/tree-sitter-fennel"
+source.rev = "ea4a536bca8997e30b22709f210f44f97e75bf7d"
path: languages.toml
user: login: the-mikedavis
body: It looks like this revision is fairly old. Is there any reason not to use the latest commit?
created_at: 2025-04-06T18:04:41Z
author_association: MEMBER
diff_hunk: @@ -396,6 +397,21 @@ indent = { tab-width = 2, unit = "  " }
 name = "elixir"
 source = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "02a6f7fd4be28dd94ee4dd2ca19cb777053ea74e" }
 
+[[language]]
+name = "fennel"
+scope = "source.fennel"
+file-types = ["fnl", "fnlm"]
+shebangs = ["fennel"]
+comment-token = ";"
+language-servers = ["fennel-ls"]
+formatter = { command = "fnlfmt", args = ["-"]}
+indent = { tab-width = 2, unit = "  " }
+
+[[grammar]]
+name = "fennel"
+source.git = "https://github.com/alexmozaidze/tree-sitter-fennel"
+source.rev = "ea4a536bca8997e30b22709f210f44f97e75bf7d"
path: languages.toml
user: login: uncenter
body: ```suggestion
source = { git = "https://github.com/alexmozaidze/tree-sitter-fennel", rev = "ea4a536bca8997e30b22709f210f44f97e75bf7d" }
```
created_at: 2025-04-08T18:26:18Z
author_association: CONTRIBUTOR

PR comments:
user: login: micampe
created_at: 2025-04-04T11:02:49Z
body: also need to add [`fnlm`](https://git.sr.ht/~technomancy/fennel/commit/acbdc71b914bee26b40e93ee9dc73a0ba9b3e68a) to the file types.
author_association: NONE
user: login: icefoxen
created_at: 2025-04-04T23:39:55Z
body: Added, thanks.
author_association: CONTRIBUTOR
user: login: icefoxen
created_at: 2025-04-08T20:21:08Z
body: Jokes aside, yes I've tested it, but probably not as thoroughly as I should.  Lots of my PR's come out of catharsis and then get mopped up after the fact if it turns out that anyone is actually interested in them.  I swear I got this to work via some kind of cursed flailing around, but upon trying again to do it ~~right~~ better, it doesn't seem to do the syntax highlighting.

```
$ HELIX_RUNTIME=~/tmp/helix/runtime cargo run -- --health fennel
Configured language servers:
  ✓ fennel-ls: ~/.local/bin/fennel-ls
Configured debug adapter: None
Configured formatter: fnlfmt
Binary for formatter: ~/.local/bin/fnlfmt
Tree-sitter parser: ✓
Highlight queries: ✓
Textobject queries: ✘
Indent queries: ✘
```

Off to a good start?  `helix/runtime/queries/fennel/highlights.scm` is present and Helix claims the language is set to `fennel`, but no highlighting.  Any pointers on how to debug this?
author_association: CONTRIBUTOR
user: login: uncenter
created_at: 2025-04-08T20:37:49Z
body: I'm getting this cryptic log error, of which I've seen before and not exactly sure how to solve 😓

```
2025-04-08T16:25:31.777 helix_core::syntax [ERROR] Could not parse queries for language "fennel". Are your grammars out of sync? Try running 'hx --grammar fetch' and 'hx --grammar build'. This query could not be parsed: QueryError { row: 132, column: 7, offset: 3284, message: "", kind: NodeType }
```
author_association: CONTRIBUTOR
user: login: icefoxen
created_at: 2025-04-09T15:27:24Z
body: Yep, ok I see the same error but it's not very enlightening.  Just points to a blank line.  However it's close, commenting out the lines just below it:

```
[ ":" ":until" "&" "&as" "?" ] @punctuation.special
(vararg) @punctuation.special
```

makes the errors go away and syntax highlighting partially work.  So, I suppose I just need to abandon voodoo, Git Gud and learn how to make `highlights.scm` work properly.  Maybe someday I'll get more than 5 hours of sleep.
author_association: CONTRIBUTOR
user: login: the-mikedavis
created_at: 2025-04-09T15:47:50Z
body: I don't see those symbols in the `grammar.js` or in [nvim-treesitter's highlights](https://github.com/nvim-treesitter/nvim-treesitter/blob/master/queries/fennel/highlights.scm) so we can remove them. If the highlighting is working otherwise we could merge this as-is (after dropping those lines) and work on improving the highlights as follow up changes.
author_association: MEMBER
user: login: icefoxen
created_at: 2025-04-11T14:03:49Z
body: Ok.  I've cleaned up the weird entries and managed to test it with the current release binary, and it works as intended.  Should be ready to go.
author_association: CONTRIBUTOR
user: login: icefoxen
created_at: 2025-04-11T14:22:04Z
body: And looking at <https://github.com/alexmozaidze/tree-sitter-fennel/commits/main/> it is mostly docs and a couple small bugfixes, so we might as well use it.  Tested it and it works. 
author_association: CONTRIBUTOR
user: login: mattly
created_at: 2025-04-25T03:53:26Z
body: I can confirm this works, HOWEVER nearly everything – including builtins like `fn`, `local`, `let` and such are highlighted as variables (the attached screenshot uses a custom theme, the only syntax type that is red is `variable`). Looking at the `highlights.scm` file, it's not clear why this is, but I'll see if I can't figure it out and submit a fix

<img width="1202" alt="image" src="https://github.com/user-attachments/assets/e97cdac7-ce2a-41ac-9e94-dd0d408778b0" />

author_association: NONE
user: login: mattly
created_at: 2025-04-25T04:24:55Z
body: Removing the final 
```
 [
   (symbol)
   (symbol_binding)
 ] @variable
```

from the `highlights.scm` file fixes the problem, apparently that's overriding all the other declarations on `(symbol)` – one of the tricky things with lisps, everything is a symbol!
author_association: NONE