Creating Language Servers for VScode
rascal-0.42.0
Synopsis
Recipes for creating an IDE for your language based on the Language Server Protocol using LanguageServer and IDEServices.
Description
Rascal's VScode extension comes with a high-level Language Server Protocol API builtin. The library module LanguageServer can be used to rapidly develop an IDE for your own domain specific language or programming language.
You can work in small steps:
- First create a Parsing Service and then Register Your Language. This gives your users:
- Syntax Highlighting which can be further configured.
- Parse error diagnostics and/or error recovery (see Parsing Service)
- Then you can optionally and independently add editor services one-by-one:
- the Selection Range Service provides an easy and quick way to select the right pieces of DSL code for the user.
- the Document Symbol Service provides a linked outline view and symbol based search in the editor.
- the Hover Service provides quick (on-demand) documentation in the editor with a tooltip. See also later Analysis Service and Build Service for pre-computing documentation information.
- the References Service and
- Definition Service and
- Implementation Service provide quick (on-demand) links to either all references, all definitions of a symbol, or all implementations of a symbol in the editor (with a hyperlink). See also later Analysis Service and Build Service for pre-computing reference, definition and implementation information.
- the Analysis Service services provides errors and warnings for the user as diagnostics in the IDE (e.g. type checking), while the user is typing in the editor.
- the Build Service triggers a compiler or another language processor. It also produces errors and warnings for the user, but only when a file has been saved.
- Execution Service provides an execution mechanism for your own editor commands that can be triggered by the user:
- Using Analysis Service or Build Service you can attach them to error messages to provide quick fixes.
- Using Code Lens Service or Inlay Hint Service which both provides information "in between the lines" of your DSL code, which actionable hyperlinks to your Execution Service commands.
- Using Code Action Service which provides a low-key lightbulb menu of context-specific actions (like quick-fixes and refactoring)
- Call Hierarchy Service provides an on-demand, lazy browser for the "call graph" of a PL or DSL.
- Rename Service offers language-specific renaming to your users.
So start with the Parsing Service and Register Your Language, then pick which IDE feature you'd like to provide to your users first, and just go with that.
Benefits
- There are only two small API modules relevant for constructing full featured IDEs:
- LanguageServer for building an LSP server that connects to the VScode client.
- IDEServices for programmatically calling IDE effects (like opening editors and starting web views).
- There is no need for a "second level" (starting up another VS code instance) to test your new extension. All you need is to Register Your Language and your language will be added to your IDE here and now.
- Your services code "sees" always the code that the user sees in their editor. Even if the file is unsaved, or the file comes from
aVeryWeirdURIScheme:///what?, all of Rascal's IO features are rerouted implicitly to see the editor contents. - Your LSP services are based on the metaprogramming facilities of Rascal, and IDE construction is just a form of meta-programming (code in -> UI information out).
- The Rascal LSP API functions are pure, and work with only immutable data-types. This makes it easy to test and/or debug independently of any editor client. Managing editor state and implementing the asynchronous LSP protocol is hidden under-the-hood.
- Your LSP services once made for VScode, will work for any IDE client we port the LSP bridge to.
- You do not have to write a full type-checker or a full compiler, to start giving your users valuable features like error checking, overviews and hyperlinks. Start small. Dream big.
- All changes to DSL files are applied via collecting ((FileSystemChange))s, which is a kind of
diffformat. If you stick with this protocol rather then writing to disk yourself, then the IDE will integrate all changes into the undo/redo stack, and can also provide confirmation dialogs and/or previews. - Later you can decide to deploy your own VScode extension with no changes to your LanguageServer services code.
Pitfalls
- It is easy to add many useful commands and features for yours users, but all of them have to have a clear and predictable semantics and all of them must be maintained in the future. It makes sense to create services "on demand", as your users ask for them. Too many options is confusing. Also not every DSL needs every programming language feature, even though it is easy to construct it with Rascal and the LanguageServer API.
- In VScode, if your Execution Service changes any DSL files on a project, which are not in an open editor, then editors are opened for each file and it is up to the user to "save" them and commit to the changes. It's the same for Rename Service and any other side-effect applied to code files you build into your Rascal code.