Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Language Server Protocol: A Language Server for DOT With Visual Studio Code- Part 1

DZone's Guide to

Language Server Protocol: A Language Server for DOT With Visual Studio Code- Part 1

Learn how to easily build support for the DOT Language using Visual Studio Code in Part 1 of this tutorial with companion code repository.

Free Resource

Modernize your application architectures with microservices and APIs with best practices from this free virtual summit series. Brought to you in partnership with CA Technologies.

A Language Server For DOT With Visual Studio Code

In a previous post, we have seen how the Language Server Protocol can be a game changer in language development: we can now build support for one language and integrate it with all the IDEs compatible with this protocol.

In this article, we are going to see how easy is to build support for the DOT Language in Visual Studio Code.

Note that Visual Studio Code now runs also on Mac and Linux.

To do this we are going to build a system composed of two elements:

  • A server which will provide support for our language (DOT)
  • A very thin client that will be integrated into Visual Studio Code

If you want to support another editor you must create a new client for that editor, but you could reuse the same server. In fact, the server is nothing else than a node app.

The code for this article is in its companion repository. In this tutorial, we are not going to show every little detail. We are going to focus instead on the interesting parts. We will start slow and explain how to setup the client and then only the parts necessary to understand how everything works. So you have to refer to the repository if you want to see all the code and the configuration details.

Language Server Protocol Recap

If you haven’t read the previous article there is a few things that you may want to know. The protocol is based upon JSON-RPC, this means it is lightweight and simple both to use and to implement. In fact there are already editors that support it, such as Visual Studio Code and Eclipse, and libaries for many languages and formats.

The client informs the server when a document is opened or changed, and the server must mantain it’s own representation of the open documents. The client can also send requests that must be fullfilled by the server, such as requesting hover information or completion suggestions.

DOT Language

The DOT Language permits to describe graphs. A tool named Graphviz can read descriptions in DOT and generate nice pictures from those. Below you can see an example. It does not matter if you are not familiar with DOT. It is a very simple language that is perfect to show you how to use the Language Server Protocol in practice.

 graph short {
     // This attribute applies to the graph itself
     size="1,1";
     a;
     // The node shape is changed.
     b [shape=box];
     // These are edges
     // You don't have to declare each node
     a -- b -- c [color=blue];
     // You don't have to use the ending ';'
     b -- d
 }

From this graph Graphviz will generate this image:

Setup

Before starting, we have to install a couple of things:

  1. VS Code extension generator: to generate a skeleton extension.
  2. VS Code extension for the DOT language: to register the DOT language.

VS Code Extension Generator

The generator can be installed and used the following way.

npm install -g yo generator-code
yo code

The generator will guide through the creation of an extension. In our case, we need for the client which is the proper VS Code extension.

VSCode Extension Generator

VS Code Extension for The DOT Language

The Language Support for the DOT language is an extension to register the DOT language with Visual Studio Code.

You can navigate to the extensions page.

Once you are there search for dot and install it.

You may wonder why we need an extension for the DOT Language. I mean, aren’t we building it one right now?

We are building a language server for the DOT Language, to implement things such as verifying the correcteness of the syntax or suggesting terms for autocompletion. The extension we are going to install provides instead basic stuff like syntax highlighting. It also associates the extension .dot files with the DOT language. These kinds of extensions are quite easy to create; they consist just of configuration files.

Structure of The Project

The project will be composed of:

  • a Language Protocol client.
  • a Language Protocol server.
  • a backend in C# and .NET Core that will provide the validation of the code.

So under the project, there will be a folder client, that will contain the client code, a folder server, that will contain the server code, and a folder csharp, that will contain the C# service. There will also be a data folder, that will contain two files with a list of shapes and colors.

Language Server Protocol architecture

The architecture of our application.

Since client and server are actually node projects you have to install the node packages. And for the .NET Core project you have to restore the nugget packages using the well known commands inside the respective folders. More precise information is available in the repository.

You have also to remember to start the C# project first (dotnet run) and then run the extension/client after that.

The Client For The Language Server Protocol

Configuration of The Client

Our client is a Visual Studio Code extension, and a very simple one, given the integrated support in VS Code for the protocol.

We are writing it using TypeScript, a language that compiles to JavaScript. We are using TypeScript for two reasons: 1) because it provides some useful features, such as strong typing, and 2) because it’s the default language for a VS Code extension.

First we are going to setup how TypeScript will be compiled into Javascript, by editing tsconfig.json.

{
    "compilerOptions": {
        "target": "es6",
        "module": "commonjs",
        "outDir": "out",
        "lib": [ "es6" ],
        "sourceMap": true
    },
    "exclude": [
        "node_modules",
        "server"
    ]
}

In addition to the usual node_modules, we exclude the folder server, because it will contain the already compiled server. In fact, to simplify the use of the extension, the client will also contain the server. That is to say, we are going to create the server in another project, but we will output the source under the client/server folder, so that we could launch the extension with one command.

To do just that you can use CTLR+Shift+B, while you are in the server project, to automatically build and output the server under the client, as soon as you edit the code.

Setting Up The Extension

In package.json we include the needed packages and the scripts necessary to automatically compile, install the extension in our development environment, and run it.

For the most part it’s the default one, we just add a dependency.

"scripts": {
    "vscode:prepublish": "tsc -p ./",
    "compile": "tsc -watch -p ./",
    "postinstall": "node ./node_modules/vscode/bin/install",
  },
  "devDependencies": {
    "@types/node": "^6.0.52",
    "typescript": "^2.1.5",
    "vscode": "^1.0.3"
  },
  "dependencies": {
    "vscode-languageclient": "^3.1.0"
  }

The same file is also used to configure the extension.

  "activationEvents": [
    "onLanguage:dot"
  ],  
  "contributes": {
    "configuration": {
      "type": "object",
      "title": "Client configuration",
      "properties": {
        "dotLanguageServer.maxNumberOfProblems": {
          "type": "number",
          "default": 100,
          "description": "Controls the maximum number of problems produced by the server."
        },
        "dotLanguageServer.trace.server": {
          "type": "string",
          "enum": [
            "off",
            "messages",
            "verbose"
          ],
          "default": "off",
          "description": "Traces the communication between VSCode and the dotLanguageServer service."
        }        
      }
    }
  },

The setting activationEvents is used to configure when should the extension be activated, in our case for DOT files. The particular value (onLanguage:dot) is available because we have installed the extension for GraphViz (Dot) files. This is also the only thing that we strictly need from the DOT extensions. Alternatively, we could avoid installing the extension by adding the following to the contributes section. But doing this way we lose syntax highlighting.

"contributes": {
    "languages": [
      {
        "id": "dot",        
        "extensions": [
          ".dot",
          ".DOT",
          ".gv"
        ]        
      }
    ],

The contributes section can also contain custom properties to modify the configuration of the extension. As an example we have a maxNumberOfProblems setting.

This is the time to call npm install so that the extension vscode-languageclient will be installed.

Setting Up The Client

Let’s see the code for the client. Copy it (or type it) in src/extension.ts

'use strict';

import * as path from 'path';
import * as fs from 'fs';

import { workspace, Disposable, ExtensionContext } from 'vscode';
import { LanguageClient, LanguageClientOptions, SettingMonitor, ServerOptions, TransportKind, TextEdit,
RequestType, TextDocumentIdentifier, ResponseError, InitializeError, State as ClientState, NotificationType } from 'vscode-languageclient';

export function activate(context: ExtensionContext) {

    // The server is implemented in another project and outputted there
    let serverModule = context.asAbsolutePath(path.join('server', 'server.js'));
    // The debug options for the server
    let debugOptions = { execArgv: ["--nolazy", "--debug=6009"] };

    // If the extension is launched in debug mode then the debug server options are used
    // Otherwise the  normal ones are used
    let serverOptions: ServerOptions = {
        run : { module: serverModule, transport: TransportKind.ipc },
        debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions }
    }

    // Options of the language client
    let clientOptions: LanguageClientOptions = {
        // Activate the server for DOT files
        documentSelector: ['dot'],
        synchronize: {
            // Synchronize the section 'dotLanguageServer' of the settings to the server
            configurationSection: 'dotLanguageServer',
            // Notify the server about file changes to '.clientrc files contained in the workspace
            fileEvents: workspace.createFileSystemWatcher('**/.clientrc')
        }
    }

    // Create the language client and start the client.
    let disposable = new LanguageClient('dotLanguageServer', 'Language Server', serverOptions, clientOptions).start();

    // Push the disposable to the context's subscriptions so that the 
    // client can be deactivated on extension deactivation
    context.subscriptions.push(disposable);
}

This is the entire client: if you remove the comments it is just a bunch of lines long.

We first create and set up the server, for normal and debug sessions.

Then we take care of the client: on line 27 we order the client to call the server only for DOT files.

On line 37 we create the client with all the options and the information it needs.

This is quite easy, given the integrated support for the Language Server Protocol in Visual Studio Code, but if you had to create a client from scratch you would have to support the protocol JSON-RPC and configure the client to call the server whenever is needed: for instance when the document changes or a completion suggestion is asked by the user.

This tutorial will be continued in Part 2. Stay tuned!

The Integration Zone is proudly sponsored by CA Technologies. Learn from expert microservices and API presentations at the Modernizing Application Architectures Virtual Summit Series.

Topics:
integration ,programming languages ,DOT ,visual studio ,language server protocol

Published at DZone with permission of Federico Tomassetti, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}