Taking the Language Server Protocol one step further
Microsoft's LSP is great for text-editor diversity, but it is severely lacking in flexibility for project-specific configuration.
Until relatively recently, choosing an editor meant choosing a big bundle: a default set of keybindings, a UI, a community, an editing philosophy, and an ecosystem of language-specific support. Different editors developed their own language-specific support (like jump-to-definition, auto-completion, display type information, formatting) in parallel. In fact, a selling point of many these editors was how advanced their support for different languages was (think PyCharm for Python or Eclipse/IntelliJ for Java).
Then Microsoft introduced Language Server Protocol. With it, editors can be combined with different LSP servers; the responsibility for language-specific logic stands on the latter's shoulders.
This dramatically reduced the amount of duplicated effort, and was widely seen as a good thing.
It turns out, however, that the configuration parameters aren't just "editor × language," but instead "editor × language × project." Almost any larger project will have a .vscode folder (and potentially also a .vim, .emacs, etc.) which sets the configuration for that project. Things like the version of software, what linter to use, whether tsserver or deno should be run on .ts files, etc. are all project-specific parameters. But currently, there is no way to configure that per-project rather than per-(project and editor).
Put differently, we successfully made an n × m problem be n + m; but the problem was actually n × m × o!
A proposed alternative
To solve this lingering issue, we could introduce an LSP proxy that intercepts requests from an editor, spins up the appropriate language server, and forwards the requests to it. There can be one configuration checked into the project repository declaring which language servers should be used for which files in the project, and with which parameters.
That configuration file might look something like this:
maps: - path: ["garn.ts"] command: deno lsp config: enable: true suggest: imports: hosts: - "https://deno.land" - "https://crux.land" - "https://x.nest.land" - path: ["*.ts"] command: typescript-language-server --stdio - path: ["*.md"] command: vale-ls
The proxy server only needs to parse the requests in order to figure out which file it relates to, and use that information (together with the project configuration) to decide which LSP to proxy to.here.)
(I tried my hand at a very quick prototype with hard-coded paramsThat's an improvement. But the developers still have to install the subservers themselves. We could however imagine combining this with docker image support, or Nix, or garn. E.g.:
maps: - path: ["garn.ts"] command: docker run --rm -it denoland/deno lsp - path: ["*.ts"] command: nix-shell -p nodePackages.typescript-language-server --command "typescript-language-server --stdio"
Conclusion
By using an LSP proxy, we can seamlessly interface with existing language servers, but allow for specifying project-specific configurations. This goes even further in fostering a more open and diverse ecosystem of editors, since the network effect of having project-specific configuration is shared by all (not just VSCode users!).
Continue Reading
We've added incremental compilation to garnix. In this blog, we discuss prior art on incremental compilation in Nix, and describe our own design.
A short note about custom typing for functions in Nix