Fennel support
At the Clojure Conj a few days ago, I mentioned I wished we had something like Calva for Fennel, and @PEZ replied that maybe we could just make Calva work for Fennel instead of rebuilding the wheel. Fennel is not a dialect of Clojure, but for the purposes of this work, I guess we can just pretend that it is and treat it like ClojureDart or Jank.
As I understand it, this would consist of several changes:
- detect
.fnland.fnlmfiles - switch the nrepl server command to a Fennel-compatible one
- switch the LSP server command to
fennel-ls - replace the list of keywords used for syntax highlighting
- maybe replace indentation rules (maybe we can skip this if formatting is delegated to LSP)
I have made some progress for the second point by updating my own nREPL server for Fennel, which now works respectably well: https://git.sr.ht/~technomancy/jeejah
However, when it comes to the changes needed in Calva itself, I am willing to give it a shot, but I think I would need some guidance for where to look. I have never before used Calva, VS Code, Typescript, ClojureScript, npm, or the official Clojure CLI, so I'm in quite a bit over my head.
OK, after doing a little bit of digging I have made some small progress. I have a few more specific questions.
detect .fnl and .fnlm files
I think this is probably done in package.json; there is a "languages" section that looks both relevant and easy to extend with a Fennel entry.
switch the nrepl server command to a Fennel-compatible one
The src/nrepl/project-types.ts file looks promising; each dialect is laid out with a fairly self-explanatory ProjectType descriptor. The only problem here is that most of the other ProjectType entries are activated by the presence of a specific file like project.clj or settings.gradle or whatever, and there is no such file to indicate when to use Fennel's jeejah nrepl server.
It's unclear to me how to connect the language defined in package.json to a Fennel-specific ProjectType.
switch the LSP server command to fennel-ls
Unfortunately the clear and declarative flow used by the nrepl client config doesn't appear to exist for LSP. I am at a loss for how to proceed on this one.
replace the list of keywords used for syntax highlighting
I found the files that seem to control this: the package.json file references a clojure.tmLanguage.json file. Unfortunately this does not exist in the repository; it appears to be auto-generated for some reason from something called a "CSON" file, and in order to update the CSON file you're supposed to install the Atom text editor? This feels unnecessarily complex; hopefully I have missed something.
However, I just realized that there's an existing bare-bones VS Code extension which has such a file. Probably we could just take this and use it directly, as it shares the same license as Calva: https://github.com/kongeor/vsc-fennel/blob/master/syntaxes/fennel.tmLanguage.json What directory should this go in?
Cool! Thanks for the summary. It still seems doable to me. 😄
detect .fnl and .fnlm files
Indeed, the extension manifest (package.json) is where the defaults for which extensions gets treated as the clojure language ID by VS Code are declared. And we can at least start by seeing how far we get by treating Fennel as Clojure here. The assumption that we're dealing with Clojure is hardcoded and spread around the Calva code base, so probably not easy to add a new language ID, but also probably doable if it comes to that.
Connect sequences and project types (nrepl server command)
The activation files... We have three Project types that we always include, without requiring an activation file: Babashka, nbb, and Joyride. The Joyride REPL start is a bit special, but I think we can treat Fennel the same way as Babashka.
Even without this, we can always connect to a running REPL, using the Generic project type. (Just FYI.)
NOTE Didn't mean to submit the only half-finished comment, but anyway, I'll put the rest of my thoughts in a separate one then...
LSP server
It's probably going to be pretty hairy to make Calva consider two different LSP servers. Doable, but quite a lot of work. It's probably easier to create a separate LSP extension for Fennel. Fennel users will have to configure Calva to not start the clojure-lsp server automatically. This could also be the extension where the file extension -> clojure connection is declared. It may be a bit weird to make this extension be a clojure extension, but we can hopefully make Calva a bit less hardcoded around the clojure language ID going forward.
There's prior art for packaging an LSP server as an extension here: https://github.com/clj-kondo/clj-kondo.lsp
Syntax Highlighting
The reason for CSON and Atom is legacy. It's how the TMLanguage files were (maybe still are) packaged for the built-in language extensions in VS Code. I never have figured out how to unit test the syntax in any other way. And it is very rare we need to touch it so it has not been a problem. It may be that the bigger problem here again is the piggybacking on the clojure language ID. I don't quite recall how the coupling works with TMLanguage definitions... One way to dodge this is to make fennel-ls provide semantic tokens for the special keywords. These tokens take precedence over TMLanguage scopes.
Formatting and indentation
Calva uses cljfmt for formatting, and also has a custom, cljfmt rules compatible, indentation engine. It's probably not easy to make Calva delegate formatting and indentation to LSP. But there is an existing mechanism where Calva fetches the cljfmt indentation rules from clojure-lsp, which could be extended to use fennel-ls. But also, Fennel users could have a cljfmt.edn file they drop in to their projects and Calva will use that.
General
It shouldn't be too much work to get a POC running, or it may prove that it is a lot of work to create a POC with any much value. If the POC indicates that the user experience gets too crappy with this “pretend Fennel is Clojure” approach, you can always rip anything from Calva that you need. ParEdit is already pretty pluggable. And the nREPL client shouldn't be too much work to extract.