Language Server Protocol (Adding Support for Multiple Language Servers to Monaco Editor)

Rahulkumarsindhav
DSC DDU
Published in
6 min readApr 25, 2020

It’s a fine day, you decide to do some coding or maybe work on your project, you pick-up your laptop, open your favorite editor or IDE, and start writing code. You see those red lines below your code and maybe some suggestions popping up while typing a method reference. It’s fascinating that IDEs and editors these days are this much smart, that they can almost tell the method name that we are typing even before we have finished typing, or are they? 🤔️

Have you ever wondered how all these smart things are happening? Well, some of you smart people might say that editors and IDEs do this by language feature extensions. You are almost right, but the real heroes behind this are the Language Servers and the Language Server Protocol(LSP). You can find its implementations over here.

In this little adventure of ours, we will conquer the art of adding support for multiple languages to the web-based Monaco Editor. To performs the following procedure you will need a Linux machine.

Monaco Editor is a powerful web-based editor written in JavaScript that powers the almighty Visual Studio Code. We will be using the monaco-languageclient from TypeFox because it supports LSP out of the box. It is an npm package that uses Monaco Editor as it’s core and provides some additional features.

So, let's start our adventure.

Step 1: Setup Language Server

Here you can find links to the repositories of Language Server for almost all the popular programming languages. For most of them to set up, you will have to compile from the source code but, some are available as standalone executable binaries. For the sake of simplicity, we will be using Theia’s typescript language server, because it is available as an npm package.

The pain when working with language servers is that they all are developed in different environments using different tools and technologies so to run them we have to set-up the environment for every one of them. Here the typescript-language-server is developed in typescript so we have to set up the NodeJS environment.

For Debian-Linux users, install the typescript-language-server by the following commands.

First, install the NodeJS and npm if not already installed.

$ sudo apt-get install nodejs
$ sudo apt-get install npm

Now install TypeScript in your NodeJS environment.

$ npm install -g typescript

Now install the npm package.

$ npm install -g typescript-language-server

Now the server is all set up and ready to run.

Step 2: Setup The Proxy Server

After the modification, our editor will talk with the use of a web-socket. There are only a few implementations of Language servers available, that can talk with web-sockets. So, to be on the safer side we will use a proxy between the editor and all the language servers. The proxy allows us to integrate multiple language servers without modifying the editor.

We are using the json-rpc-ws-proxy to serve the purpose. This is the smoothest walk of our adventure.

This project uses the NodeJS environment, but we have set it up with the language server so, no need to worry.

Clone the repository from git.

$ git clone git@github.com:wylieconlon/jsonrpc-ws-proxy.git

Setting up the proxy for multiple language servers is pretty much straight forward. It uses a YAML file (servers.yml) to specify the executables for the language servers with their command-line arguments. The YAML file looks something like this when we add the typescript server configurations.

Here the first line is the executable of language server and the second line is command-line arguments that specify that the server will use STDIO mode for communication.

But let’s say if you want to add the language server ‘ccls’ for C/C++ also, or any other language then you just have to add another element to the langservers array in servers.yml file.

Now we are all set up with the proxy and language server.

Step 3: Modify monaco-languageclient

We will not use the npm package itself but rather we will clone the source repository from git and make some modifications to it.

First clone the TypeFox’s monaco-languageclient repository from GitHub.

$ git clone https://github.com/TypeFox/monaco-languageclient.git

This repository already contains an example under the ‘example’ folder that demonstrates the integration with VS Code’s JSON server. Go ahead and run the example to see the magic. Compilation and running instructions can be found in the repository’s ‘Readme.md’ file on GitHub.

So, now that you have seen it in action, let’s make some modifications.

Our all modifications will be within the /example folder itself.

First, change the server.ts file accordingly.

Most of the code in the shipped server.ts file is for creating web-socket and instantiating the JSON language server. But since we are going to put language servers behind the proxy, we don’t need that code. This code only serves the static JavaScript files generated by the web-pack.

Now go and take a look at client.ts and main.ts files, you will see that they are using the Monaco Core editor here. It is fine, but you don’t get the syntax highlighting and theming support with that. So, we are going to replace it with Monaco Editor.

Install the Monaco Editor package into the project (inside /example).

$ npm install monaco-editor

In main.ts file replace ‘monaco-editor-core’ with ‘monaco-editor’ in the require statement.

The implementation that we are using is served as a JavaScript bundle. We are bundling the code using Webpack.

Monaco Editor uses TTF files for syntax highlighting. So we have to configure the webpack to read those TTF files also. In the /example/webpack.config.js file inside common/module/rules array add the following object.

This tells the web-pack to collect the TTF files using the file-loader module. So, to use this we must install the file-loader module.

$ npm install file-loader

Now, we will make some changes inside the client.ts file to open a web-socket connection with the proxy server.

The editor uses the file system of your machine to store the code of the text editor temporarily, so you need to create a directory structure as shown below.

demo
├── cpp
│ └── file.cpp
└── ts
└── file.ts

Now modify the client.ts file as following.

How to run?

Now that everything is set up, let's run the system.

First, we will start the proxy server by the following command (fire inside the json-rpc-ws-proxy repository folder).

$ node dist/server.js --port 3000 --languageServers servers.yml

After the proxy server successfully starts, its time to web-pack our client code and serve it.

Move to the monaco-languageclient/example directory and fire below commands.

If you are running this first time, install the packages by :

$ yarn install

After all the node modules are installed, we will build the project.

$ yarn run build

After everything is built up and packed, serve the web-pack by the following command.

$ yarn run start

Now open a browser and go to localhost:4200/, you will see the magic…

You can open chrome developer tools and go to the Network tab and select ‘ws’ to see the web-socket traffic and LSP messages passed between the editor and the proxy server.

What many web-based editors do is they dump a javascript file on the browser and use that as a language server. Here the problem is performance because language servers eat up quite a bit of CPU usage time, this may make the editor feel like lagging. And also it is dependent on the client browser, so if someone turns off the JavaScript support, the whole thing falls apart.

The approach used here moves the heavy lifting to the servers. The proxy server along with language servers can be deployed on the cloud for high scalability and performance.

--

--

Rahulkumarsindhav
DSC DDU
Writer for

Computer engineering student, tech enthusiastic.