Implementing a Language Server… How Hard Can It Be? — Part 3

Nipuna Marcus
Ballerina Swan Lake Tech Blog
7 min readJan 14, 2020

Recap …

We implemented HelloLS in the previous part (part 2) of this series for the language called “HeloFalks” with the file extension “.hello”. So to test the HelloLS lets implement a simple VSCode plugin with language support.

Let’s Begin with the VSCode plugin …

So the easiest way to start a VSCode plugin is to generate one (Well I mean this is the easiest way with everything programming). There is a tool called Yeoman created by a bunch of cool guys which helps developers to kickstart a new project. So you can install Yeoman using the following command with the code extension generation support.

npm install -g yo generator-code

now you know you need *Node* installed on your machine.

Then to generate the VSCode extension you can use the following command and will have to go through an interactive configuration phase to generate the extension.

yo code

This is how I configured it to generate a typescript based extension.

Yeoman configuration applied when generating a VSCode extension

The extension will be generated in the directory where you ran the above command under the new directory named “helloFalks”. So open it in VSCode using the following command.

cd helloFalkscode .

You can get more details like how to run and to debug the extension from VSCode docs and you can learn the structure of the VSCode plugin using Extension Anatomy.

Now this generated plugin is a common VSCode language that only activates for a command. So how can we make it activate for HelloFalks language?

It’s easy. What you have to do is open the package.json file available in your extension dir and (Assuming you read Extension Anatomy doc) update the *contributes* object as below.

"contributes": {
"commands": [
{
"command": "extension.helloWorld",
"title": "Hello World"
}
]
"languages": [{
"id": "hello",
"aliases": ["HelloFalks", "hello"],
"extensions": [".hello"],
"configuration": "./language-configuration.json"
}],
"grammars": [{
"language": "hello",
"scopeName": "source.hello",
"path": "./syntaxes/hello.tmLanguage.json"
}]
}

So you see two new properties in your contributes object …

  1. languages — this contains the details of the language that this plugin supported (I mean configuring a file extension(s) that this plugin supports and activates on). Language ID and file extensions are given in the configuration.
  2. grammars — this contains the syntax highlighting grammar for the language that this plugin supports. the path for the grammar file is given in the configuration.

Well instead of posting what those files look like in here I’ll tell you how to generate those files using the Yeoman.

creating a language support extension

you can copy grammar and language files into your typescript extension. I don’t know whether yeoman supports typescript extension with language support as I didn’t try all of there options.

Okay now as we got our extension generated let’s prep it for connecting a language server.

Integrating LS client into VSCode …

So there is an LSClient NPM module available for the VSCode that we can import and connect the LSP to VSCode using that.

So let’s add the “dependencies” property to package.json for VSCode language client.

"dependencies": {
"vscode-languageclient": "5.1.1"
}

then let’s go to the extension.ts file (which is available in the “src” dir)and add the import.

import { LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient';

after adding the dependency make sure to do an NPM install in the extension directory to get the dependencies that needed…

npm install

Okay, so we are almost there now.

Let’s Implement the Extension…

So go into the src/extension.ts and open it up in your favorite editor ( well… VSCode in this case ).

Let me explain a bit about this code. This is the file that contains the entry point of the extension. So the activate function will be called upon the activation of the plugin and all of your logic should be placed here. So code is explained in the file using comments but I’ll just walk through it quickly. so you can see the import of vscode-laguageclient and you already know what it is. See you already know stuff. So this is the client that we gonna use to interact with the LS we wrote. Then inside the main entry point for the plugin, active function, we get the JAVA_HOME from the environment of the process and then if JAVA_HOME is available we gonna run a .jar called launcher.jar . This is the .jar generated by building the launcher module in LS which contains the launcher for LS. So if you look in this location in the hellofalks VSCode plugin you can see the launcher.jar. So inside the active function what we gonna do is run this .jar using the current java installation available in the local machine.

Then we set the server options through the client, what to pass into the LS and what CLI commands to run to startup the server and what argument to be passed into the CLI command.

// Name of the launcher class which contains the main.
const main: string = 'StdioLauncher';
~~// Java execution path.
let excecutable: string = path.join(JAVA_HOME, 'bin', 'java');
// path to the launcher.jar
let classPath = path.join(__dirname, '..', 'launcher', 'launcher.jar');
const args: string[] = ['-cp', classPath];// Set the server options
// -- java execution path
// -- argument to be pass when executing the java command
let serverOptions: ServerOptions = {
command: excecutable,
args: [...args, main],
options: {}
};

Then we add the options to enable the language client.

// Options to control the language client  let clientOptions: LanguageClientOptions = {
// Register the server for plain text documents
documentSelector: [{ scheme: 'file', language: 'hello' }]
};

You can see that we have added the documentSelector property for the client and we have mentioned that the language is hello and the scheme is a file. So this is where we tell the client that it has to have these types of documents to activate the LS and get a response.

Then we start the language client by passing in the server options and client options.

let disposable = new LanguageClient('helloLS', 'HelloFalks Language Server', serverOptions, clientOptions).start();

So you can see this disposable and you can use this to remove the language client after deactivation of the extension(So basically when VSCode is shut down) in the deactivate function which is another event in VSCode that occurs on the shut down of VSCode.

So basically deactivate function is there to clean up any other processes, in our case, it is the language server process and frees the OS resources.

So now as we have done with the implementation of the extension you can go to the VSCode left side panel and select debug panel and run this as an extension. That will run a separate VSCode instance with your extension installed. So you can try it out with a file with extension .hello . just type say and see the magic happens.

helloFalks VSCode plugin in action

If you see a pause before I input the s character in the editor that I was waiting for the plugin to activate and LS to start.

So this is all fun but now how you can package this VSCode plugin so you can give it to other people to install in their VSCode. Here is a guide you can follow on how you can package and create a .vsix and publish your plugin on the VSCode market place.

btw to create .vsix you need to add the publisher name "publisher":"hellofalks" and edit your README.md which is generated by the tool we used to generate the VSCode plugin. We have to edit the description of the plugin at least to package the plugin (for testing purposes remove the “This is the README of your extension” from the description in README.md and it will work). But remember if you publish this plugin README.md will be the main page for your plugin on VSCode market place.

Okay…. So that’s it… we have implemented a VSCode plugin with the language server support for our language HelloFalks.

Please try all the steps by your self and do research on your own (I mean google) and try other ways you can create a VSCode plugin and ask if you have any questions and I will try to answer them.

Also if you want you can have a look at Ballerina VSCode plugin and Ballerina Language server implementation.

Ballerina VSCode plugin: https://github.com/ballerina-platform/ballerina-lang/tree/master/tool-plugins/vscode

Ballerina Language Server: https://github.com/ballerina-platform/ballerina-lang/tree/master/language-server

Enjoy!!!

--

--

Nipuna Marcus
Ballerina Swan Lake Tech Blog

Software Engineer at Zuhlke, Ex Tech Lead at WSO2, Author of “Language Server Protocol and Implementation”