Skip to content

Fennel language support #3846

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
gasacchi opened this issue Sep 15, 2022 · 9 comments · May be fixed by #13260
Open

Fennel language support #3846

gasacchi opened this issue Sep 15, 2022 · 9 comments · May be fixed by #13260
Labels
C-enhancement Category: Improvements

Comments

@gasacchi
Copy link

gasacchi commented Sep 15, 2022

Hi, how to add Fennel language support?
at least syntax highlighting, i came across tree-sitter-fennel
and basic fennel lsp fennel-ls lsp completion work fine but i cannot build tree-sitter-fennel

try to setup locally as adding language

my languages.toml:

# Fennel language support
[[language]]
name = "fennel"
scope = "source.fennel"
injection-regex = "fennel"
file-types = ["fnl"]
roots = []
comment-token = ";;"
language-server = { command = "fennel-ls" }
indent = { tab-width = 2, unit = "  " }

[language.auto-pairs]
'(' = ')'
'{' = '}'
'[' = ']'
'"' = '"'

[[grammar]]
name = "fennel"
source = { path = "/home/gasacchi/Dev/fork/tree-sitter-fennel" }

and try to helix -g build
the output:

Building 108 grammars
107 grammars already built
1 grammars failed to build
        Failure 0/1: Parser compilation failed.
Stdout:
Stderr: In file included from /home/gasacchi/Dev/fork/tree-sitter-fennel/src/parser.c:1:
/home/gasacchi/Dev/fork/tree-sitter-fennel/src/parser.c: In function ‘ts_lex_keywords’:
/home/gasacchi/Dev/fork/tree-sitter-fennel/src/tree_sitter/parser.h:136:8: warning: variable ‘eof’ set but not used [-Wunused-but-set-variable]
  136 |   bool eof = false;             \
      |        ^~~
/home/gasacchi/Dev/fork/tree-sitter-fennel/src/parser.c:3069:3: note: in expansion of macro ‘START_LEXER’
 3069 |   START_LEXER();
      |   ^~~~~~~~~~~
/usr/bin/ld: cannot open output file /var/lib/helix/runtime/grammars/fennel.so: Permission denied
collect2: error: ld returned 1 exit status

even with sudo helix -g bulid

Building 107 grammars
107 grammars already built

it ignore fennel tree-sitter, is there any mistake on my end? Thanks

@gasacchi gasacchi added the C-enhancement Category: Improvements label Sep 15, 2022
@Ordoviz
Copy link
Contributor

Ordoviz commented Sep 15, 2022

Did you install Helix with a package manager? Try cp -r /var/lib/helix/runtime ~/.config/helix/ and launch Helix with RUNTIME=~/.config/helix/runtime. To make highlighting work, you will also have to write a ~/.config/helix/runtime/queries/fennel/highlights.scm file.

@gasacchi
Copy link
Author

Did you install Helix with a package manager? Try cp -r /var/lib/helix/runtime ~/.config/helix/ and launch Helix with RUNTIME=~/.config/helix/runtime. To make highlighting work, you will also have to write a ~/.config/helix/runtime/queries/fennel/highlights.scm file.

it works thanks 😄

@Ordoviz
Copy link
Contributor

Ordoviz commented Sep 17, 2022

There is a pull request for Fennel support: #3851

@icefoxen
Copy link

The PR is stalled 'cause we already have tree-sitter-scheme (which might be weirdly huge?) and people were wondering if it would be preferable to have a single "lisp mode" or "sexpr mode" that handles any Lisp. I don't think that's a good idea 'cause lisps don't actually all have the same syntax... But I was thinking we might be able to do a "clojureish mode" that is Good Enough for Clojure, Fennel and Janet, and then a "True Lisp Mode" for Scheme, CL, LFE, etc. I want to try this out and see how well this works, no idea how easy it would be or not be to add partially-overlapping languages like that.

If I don't have a proof of concept in two months though assume I've gotten distracted by shiny objects and wandered off.

@Mehgugs
Copy link

Mehgugs commented Jul 20, 2023

I dont think that is a good idea at all. Imagine we had a grammar for "c-like" languages, maybe you can develop an lsp that provides some basic autocomplete. You wouldn't want that to be used as your editor's grammar for typescript.

@offsetcyan
Copy link
Contributor

Unifying the tree-sitter grammar among all Lisps is the kind of yak shaving I would expect from lispers but really a blocker on having adequate support for each of them. I here to +1 any kind of upstream support for Fennel.

@atomicptr
Copy link
Contributor

I feel like this is silly as a blocker for adding Fennel esp. considering this was not a problem for janet #9247. I'm not sure if merging all lisps into one grammar is a good idea because most of them do have some special quirks.

The sentiment might have changed around this though (considering the janet one was merged without discussions) and it might be worthwhile submitting a new PR for this.

I played around with the scheme and Clojure (which is much closer to Fennel than scheme) grammar but decided in the end to just use the fennel one since that works best.

For completions sake, this is my current config that works quite well:

xdg-config/helix/languages.toml

[language-server.fennel-ls]
command = "fennel-ls"

[[language]]
name = "fennel"
scope = "source.fnl"
injection-regex = "(fennel|fnl)"
file-types = ["fnl"]
shebangs = ["fennel"]
roots = []
comment-token = ";"
indent = { tab-width = 2, unit = "  " }
formatter = { command = "fnlfmt", args = ["-"] }
language-servers = [ "fennel-ls" ]
grammar = "fennel"

[[grammar]]
name = "fennel"
source = { git = "https://github.com/travonted/tree-sitter-fennel", rev = "15e4f8c417281768db17080c4447297f8ff5343a"}

xdg-config/helix/runtime/queries/fennel/highlights.scm (from: #3851)

(comment) @comment

[ "(" ")" "{" "}" "[" "]" ] @punctuation.bracket

[ ":" ":until" "&" "&as" "?" ] @punctuation.special

(nil) @constant.builtin
(vararg) @punctuation.special

(boolean) @constant.builtin.boolean
(number) @constant.numeric

(string) @string
(escape_sequence) @constant.character.escape


((symbol) @variable.builtin
 (#match? @variable.builtin "^[$]"))

(binding) @symbol

[ "fn" "lambda" "hashfn" "#" ] @keyword.function

(fn name: [
  (symbol) @function
  (multi_symbol (symbol) @function .)
])

(lambda name: [
  (symbol) @function
  (multi_symbol (symbol) @function .)
])

(multi_symbol
  "." @punctuation.delimiter
  (symbol) @variable.other.member)

(multi_symbol_method
  ":" @punctuation.delimiter
  (symbol) @function.method .)

[ "for" "each" ] @keyword.control.repeat
((symbol) @keyword.control.repeat
 (#eq? @keyword.control.repeat
  "while"))

[ "match" ] @keyword.control.conditional
((symbol) @keyword.control.conditional
 (#match? @keyword.control.conditional "^(if|when)$"))

[ "global" "local" "let" "set" "var" "where" "or" ] @keyword
((symbol) @keyword
 (#match? @keyword
  "^(comment|do|doc|eval-compiler|lua|macros|quote|tset|values)$"))

((symbol) @keyword.control.import
 (#match? @keyword.control.import
  "^(require|require-macros|import-macros|include)$"))

[ "collect" "icollect" "accumulate" ] @function.macro
((symbol) @function.macro
 (#match? @function.macro
  "^(->|->>|-\\?>|-\\?>>|\\?\\.|doto|macro|macrodebug|partial|pick-args|pick-values|with-open)$"))

; Lua builtins
((symbol) @constant.builtin
 (#match? @constant.builtin
  "^(arg|_ENV|_G|_VERSION)$"))

((symbol) @function.builtin
 (#match? @function.builtin
  "^(assert|collectgarbage|dofile|error|getmetatable|ipairs|load|loadfile|loadstring|module|next|pairs|pcall|print|rawequal|rawget|rawlen|rawset|require|select|setfenv|setmetatable|tonumber|tostring|type|unpack|warn|xpcall)$"))

(list . (symbol) @function)
(list . (multi_symbol (symbol) @function .))
(symbol) @variable

@mattly
Copy link

mattly commented Feb 11, 2025

any progress on this? I do regular work in Fennel, and while I would miss REPL integration support until plugins land, this is the main thing keeping me from using helix as my daily driver.

@icefoxen
Copy link

icefoxen commented Apr 3, 2025

Some Person On IRC(tm) said that they got Fennel working with this:

[language-server.fennel-ls]
command = "fennel-ls"

[[language]]
name = "fennel"
scope = "source.fennel"
file-types = ["fnl"]
language-servers = ["fennel-ls"]
formatter = { command = "fnlfmt", args = ["-"]}

[[grammar]]
name = "fennel"
source.git = "https://github.com/alexmozaidze/tree-sitter-fennel"
source.rev = "ea4a536bca8997e30b22709f210f44f97e75bf7d"

Their highlights.scm:

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

(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

[
  (string)
  (string_binding)
] @string

[
  (symbol)
  (symbol_binding)
] @variable

icefoxen pushed a commit to icefoxen/helix that referenced this issue Apr 3, 2025
Closes helix-editor#3846 .

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 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.
@icefoxen icefoxen linked a pull request Apr 3, 2025 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-enhancement Category: Improvements
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants