Dec 19, 2023Alex David

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 alternativeshare

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.? (I tried my hand at a very quick prototype with hard-coded params here.)

That'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"

Conclusionshare

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

Nov 16, 2024Julian K. Arni

We've added incremental compilation to garnix. In this blog, we discuss prior art on incremental compilation in Nix, and describe our own design.

Nov 11, 2024Julian K. Arni

How we designed our private caches.

Aug 27, 2024Julian K. Arni

A short note about custom typing for functions in Nix

View Archive
black globe

Say hi, ask questions, give feedback