Integrate Language-Server with VS-Code extension
This week my team and I wanted to create a new Language Server for a company internal language. There have already been efforts to create an Eclipse Language Plugin which was based on XText. In order to be able to use the Language Server on other IDEs than Eclipse, we created a new Language Server based on Microsofts Language Server Protocol (LSP).
My part was to integrate the newly created Language-Server with VS-Code. I chose this Editor over IntelliJ IDEA since it is known to have an easier and better documented integration for Language-Servers than IntelliJ has.
Communication between Client and Language-Server
First of all, when the user opens a document, the client sends a notification with the content of the document to the Language Server.
The server sends a notification witht the Diagnostics of the current document back to the client.
If the user edits a document, the client sends a new notification with the current url of the document and the differences compared to the old version. This way it does not have to upload the whole document after every change.
If the user executes “Go to definition” he sends a Request to the server with the current documentURI and the position of the cursor.
The server sends back a response with the location of the linked class, method or variable.
When the user finally closes the document, it sends a notification with the documentURI to the server so that it can remove the document from its current context.
The default port for Language-Servers is port 5007. In this example we will also start our Language-Server on this port.
You can read more about the communication between server and client on https://code.visualstudio.com/api/language-extensions/language-server-extension-guide
Creating the extension
To create a language extension, you need to create a project with the following project structure:
Where <extension> should be the extension of the new language that you want to support.
In the following sections of this tutorial, I will continue using “gui” as the extension of the language files.
The content of the package.json file looks like this:
Keep an eye on the parts from line 22 til 35 since this is the extension related part. It is really important that you add the dot before the extension name on line 33.
The gui.configuration.json file will be used for local support of the language like auto-closing brackets and comments.
All these configurations are needed to improve the workflow of the enduser and providing basic functionalities for autocompletion.
The basic /src/tsconfig.json file looks like this:
Now the really interesting part is located in the extension.ts file.
A LanguageClient gets created with a connection to port 5007 on localhost.
On line 27 the gui extension is needed, so that the server knows which files he should track.
The fileEvents that should notify the server are defined on line 29. With this configuration the client only notifies the server if a “.gui” file changed.
On line 37 the client is started.
The deactivate() function is needed so that the server can close the connection if the extension gets disabled.
Running the extension
To run the extension, you need to open this project in VS-Code, if you are not already working with it, and press “Debug/Run”. This will open a new VS-Code window with you custom extension already installed and enabled. Now you can create a new file with the defined extension. You should now be able to use autocomplete functionality and semantic checking of your LSP server.
Further information can be found on these sites:
What went good
I think that the actual implementation of the extension was not that hard and I got it working in a pretty short time.
What needs improvement
The hardest part in this small project was to understand what exactly the Language Server Protocol is and how it works. At the beginning I did not read that much in the official docs but rather tried to find answers to specific questions I had at that moment on StackOverflow. The Problem with that approach was that I never had a full understanding of the topic itself and therefore ran into issues that I would not have had if I just informed myself a little bit better about that topic beforehand.