Overview

The Language Server Protocol is used between a tool (the client) and a language intelligence provider (the server) to integrate features like auto complete, goto definition, find references, etc.

You can learn more about the language server specification on the LSP GitHub page.

Currently Eclipse Che implements the 3.x protocol version.

Note that, Eclipse Che also implements the snippet syntax used in VSCode. It is not versioned in the LSP specification, but the supported syntax is described here.

Adding Support for New Languages

There are two approaches to add a new language server:

  • via installer and launcher: this way a language server runs in the machine where the respective installer has been enabled

  • adding language server as a sidecar in workspace configuration - multi-machine recipe + server with required attributes

General Concept

Language server integration is divided into 2 steps: an install followed by a separately triggered start. Language servers aren’t started when the agent starts. Instead they are started in a second step which can be triggered at any time. This is done to reduce resource consumption and reduce workspace startup time.

  1. The language server agent is launched when the workspace starts - its job is to install all dependencies and prepare the bash launcher file that will be used to start the language server.

  2. The launcher is triggered and starts the language server. We suggest triggering the launcher when the user begins interacting with file types related to the language server. Once launched, the language server is registered with specific file types (covered in more detail below).

Adding a Language Server Installer

Follow the documentation on how to add new installer.

Examples of existed language server agents you can learn from:

Adding a Language Server Config

In order to start/initialize a language server for the desired language, you need to implement LanguageServerConfig interface which pretty much defines what needs to be accomplished to start a 'local' language server.

Here is how a typical LanguageServerConfig looks like: Clangd.

Things to pay attention to:

  • REGEX defines regexp for desired files. It can be a path, all fines with certain extension or particular file names etc.

  • launchScript is usually created by an installer script and contains command(s) to launch a language server in a stdio mode

  • LANGUAGE_ID is defined in a guice module. See: ClangdModule

  • add binding in guice module

Installers are packaged into wsmaster, so you will need to add required dependencies there. LanguageServerConfigs are usually part of plugins packages with a workspace agent.

LS-Sidecars

While the above approach works well for custom assemblies, i.e. you actually need to rebuild Che with a custom plugin that registers a new installer and a language server launcher, there is a mechanism to launch Language Servers in parallel containers/sidecars. This is what you need to do to add a new language server to your workspace as a sidecar.

  • Build a Docker image in which language server is started in ENTRYPOINT or CMD. Note that some language servers support tcp arguments in their start syntax. Make sure that the language server acts like a server, rather than attempts to bind to a socket. The best way to check it is to run the image: docker run -ti ${image}. If the container starts, everything is fine, and if it exits immediately, you need to fix it.

We recommend running language servers in stdio mode and use sockat as a proxy. Here’s an example of a Dockerfile that builds an image with TyperScript language server:

# inherit an image with node.js and npm - runtime requirements for TypeScript LS
FROM eclipse/node

# install socat
RUN sudo apt-get install socat -y && \
    sudo npm install -g typescript@2.5.3 typescript-language-server@0.1.4

# run socat that listens on port 4417 with exec command that starts LS
CMD socat TCP4-LISTEN:4417,reuseaddr,fork EXEC:"typescript-language-server --stdio"
  • Create a stack with a custom recipe: Create Workspace > Add Stack:

services:
 typescript-ls-machine:
  image: ls/image
  mem_limit: 1073741824
 dev-machine:
  image: eclipse/ubuntu_jdk8
  mem_limit: 2147483648
  depends_on:
   - typescript-ls-machine
  • In User Dashboard, go to Workspaces > Your Workspace > Config, and add a server for typescript-ls-machine in servers:[]

"servers": {
  "ls": {
    "attributes": {
      "id": "go-ls",
      "internal": "true",
      "type": "ls",
      "languageRegexes": "[ {\"languageId\":\"golang\", \"regex\":\".*\\\\.go$\"}]",
    },
    "protocol": "tcp",
    "port": "4417"
  }
}
  • ls - server name - can be any string

  • attributes.id - can be any unique identifier

  • attributes.internal - true. Mandatory! Used to get an internal link to server

  • attributes.type - ls. Mandatory. Used by the IDE client to identify a server as a language server

  • languageRegexes.languageId - language identifier, either one of those supported in LSP specification or own.

  • languageRegexes.regex - regexp expression to match either extension or file name + extension, or whatever match you need (for example, path, say, initialize language server only for config/config.xml files). Pay attention to regexp syntax since errors are not validated by server, and bad regexp will result in the client ignoring your files.

  • In User Dashboard, go to Workspaces > Your Workspace > Volumes, add a volume for each machine. The two volumes have to share the same name (for example, projects) and path /projects so that they actually share one volume. This way a language server container has access to workspace project types.

volumes ls
  • Start a workspace. Open a file with one of the extensions bound to a language ID. Che client will attempt to connect to language server over tcp socket. This data is retrieved from workspace runtime. Language server process should be available at the port declared in the server. You can either use Socat or launch a language server in tcp mode if it supports it. It is your Docker image’s responsibility to launch the language server. Adding ENTRYPOINT or CMD instruction should work well.

See: Sample configuration of a workspace featuring 2 machines, one of which is a language server machine.