Skip to content

Language Server Development

Sören Domrös edited this page Aug 16, 2022 · 4 revisions

The KIELER language server (KIELER LS) uses Xtext to generate language features and to provide a language server. Sprotty is used as the underlying diagram server implementation. On the client-side is some kind of application, which communicates using an extended language server protocol (LSP). This guide is for developers who want to use the KIELER LS or KlighD Diagram Server (names are not final) or extend it and connect some kind of application to it (we give a Theia, VSCode, and standalone web application example).

Starting the LS (debug mode)

We assume that you are using Eclipse and used one of our or your Oomph setups to configure yourself a nice development environment with all sources inside.

We further assume that you are using some class to start your language server, let's call it de.cau.cs.kieler.language.server.LanguageServer  and it resides the de.cau.cs.kieler.language.server  project (as it is currently the case for the semantics language server in the KIELER project).

To start your language server in debug mode you need to start some client-side application that will speak to your language server. Since you want to debug your LS you cannot connect via stdin/out but must connect via socket. For the sake of this guide, we will use the socket  5007  as your LSP socket. If you are using Theia or some kind of standalone application do not confuse this socket with the socket your application runs on.

Create a new debug configuration for a Java Application in Eclipse: Screenshot from 2021-06-17 09-07-29

Next, you need to specify your arguments to connect via socket and include all dependencies.

Add -Dport=5007 as a VM argument (in the arguments tab). This will be used by the AbstractLanguageServer to establish a connection (the specific class might change). -Djava.awt.headless=true since it is necessary on a Mac.

Screenshot from 2022-08-16 12-45-05

Such a run configuration is included in the sematics (keith) setup for the semantic and the semantic and klighd. If you want to also add ELK from your development Eclipse to the LS you have to create a new configuration and add all projects and their folders (via advanced add folders) to the classpath.

Screenshot from 2022-08-16 12-46-11

Include all dependencies by either adding all relevant projects and their folders to the classpath or, which might be easier if it does not cause any problems, add all projects and their folders to your classpath. You can add folders to your classpath by clicking on the classpath, selecting Advanced... and selecting Folder.

The following is printed in the console when the LS starts.

Connection to: localhost:5007
Starting language server socket

How to use (Kieler)ServiceLoader

Classes that are provided via a ServiceLoader have a contribution that provides them. This contributions share a common interface lets call it IStuffContribution. A specific StuffContribution (it provides stuff) is the ImportantStuffContribution.

// defined in some common package
interface IStuffContribution {
    
    abstract def IStuff getStuff()
}
// defined in the package that holds ImportantStuff
class ImportantStuffContribution implements IStuffContribution {
    override getStuff() {
        return new ImportantStuff()
    }
}

The ServiceLoader has to know that some plugin provides an implementation for IStuffContribution. Therefore, a folder named services is added next to the corresponding MANIFEST.MF.

In the services folder a file named the same as the fully qualified name of the implemented interface is added. Here this one is called de.cau.cs.kieler.basic.package.IStuffContribution.

The file holds the fully qualified names of all implementations of this interface:

de.cau.cs.kieler.important.package.ImportantStuffContribution

Now you can access implementation of IStuff as follows:

var stuffList = newArrayList
for (stuffContribution : KielerServiceLoader.load(IStuffContribution)) { // dynamically load all contributions that provide stuff
	// Add all stuff to a list of stuff
	stuffList.add(stuffContribution.getStuff())
}