Codemirror 6 and Typescript LSP

Hey, I’ve been able to get this working pretty well (autocomplete, type error lints etc.), so I’m more than happy to help. One of my goals was to build an editor that worked without a backend running the LSP server, so all of this happens in the browser only.

Unfortunately my code isn’t open source (yet), so I cannot point you to a repository, but here’s the overall gist of what I did:

  1. When you talk about the Typescript LSP, I’m assuming you’re talking about tsserver, which ships with Typescript and is the “brains” behind the compiler, so to speak.
  2. Don’t try to run tsserver on your own. While I did manage to do it inside a WebWorker, it’s far too tedious. The Typescript folks maintain @typescript/vfs - npm, which is exactly what you want (and is fully typed!). I would also recommend fetching core TS types from the Typescript CDN using createDefaultMapFromCDN. (I personally had my own CDN setup, but the public TS one works just fine).
  3. It is also worth it to understand tsserver commands. Their typings are a great place to do that: TypeScript/protocol.ts at main · microsoft/TypeScript · GitHub
  4. Once you get Typescript working, wiring it up to CodeMirror is a matter of using the ts.languageServer instance in the correct places.
  5. For linting (showing up type errors as squiggly lines) you’ll want to use the linter extension from @codemirror/lint. Inside the lint source, I make a call out to ts.languageService.getSemanticDiagnostics
  6. For autocomplete, I also found the blog post Marijn linked, but it didn’t prove very useful for my case. Instead, inside the override function of the autocomplete extension, I make a call out to ts.languageService.getCompletionsAtPosition. This is very inefficient though, as the call will happen on every keystroke, debounced (it’s not really noticable for small lists because tsserver is built to be fast), so I’m still trying to figure out a better way. tsserver also has something called CompletionEntryDetails, which returns more “details” about an autocomplete item. They’re meant to be used to render details about the currently highlighted autocomplete item, but I’m yet sure how to get this information from CodeMirror, so I also fetch CompletionEntryDetails for all autocomplete items using ts.languageService.getCompletionEntryDetails. This makes it even more expensive, so be aware.
  7. You can also use the hoverTooltip extension from @codemirror/tooltip. In here, I make a call out to ts.languageService.getQuickInfoAtPosition.
  8. One last thing to remember is that you need to keep tsserver's view of the currently open “file” (tsserver deals in terms of files) up-to-date with what you see. To do this, I override the dispatch function of the EditorView, and make a call out to ts.updateFile from there, AFTER updating the view. make sure you debounce this to avoid UI freezes.

I’m still actively working on this TS + CodeMirror powered editor, so some things are far from perfect (and other features are missing), but overall it works pretty well!
A motivating factor for me was looking at Monaco Editor that the TS folks use on the TS Playground (typescriptlang.org/play/). That does not talk to a backend running an LSP server either, so everything they do in there is possible in the browser. Good luck!

10 Likes