Skip to main content

Meet LSP4IJ, a new Debug Adapter Protocol for JetBrains-based IDEs

· 9 min read
Angelo Zerr
Principal Software Engineer @ Red Hat

LSP4IJ is a free and open-source Language Server Protocol (LSP) client for JetBrains-based IDEs, compatible with both community and enterprise flavors.

Since the article Meet LSP4IJ, a new LSP Client for JetBrains-based IDEs was written, LSP support has improved considerably:

The latest version of LSP4IJ includes all of these improvements to LSP support, but what sets it apart from other releases is that it provides a new, free and open-source Debug Adapter Protocol (DAP) client for JetBrains-based IDEs, compatible with both community and enterprise flavors.

Here is a list of some LSP improvements. To know in detail all the improvements, it is recommended to read the LSP support section.

LSP features

LSP hover now supports syntax coloration for markdown code blocks :

LSP Hover / markdown

LSP on type formatting is now supported:

On Type Formatting

LSP selection range is now supported:

LSP selection range

LSP workspace symbol is now supported:

LSP workspace symbol

LSP call hierarchy is now supported:

LSP Call Hierarchy

User defined Language Server

Server configuration is done with a JSON editor that can be associated with a JSON schema to provide validation, completion, and hovering when editing the server configuration:

Set Breakpoint

Debug Adapter Protocol (DAP)

LSP4IJ's DAP support

  • allows to connect to the DAP server via stdio and socket
  • and handle both types of request launch and attach.

Here is a list of DAP servers that have been tested to implement this DAP support:

DAP Serverstdiosocketlaunchattach
Go Delve DAP serverXX
Julia DAP serverXX
Python Debugpy DAP serverXX
Swift DAP ServerXX
VSCode JS Debug DAP ServerXX

VSCode JS Debug

Takes a sample with vscode js debug

To debug JavaScript or TypeScript files, you can use the VSCode JS Debug DAP server.

Let’s debugging the following test.js file:

const s = "foo";
console.log(s);

Set Breakpoint

Configure DAP server

  1. Download the js-debug-dap-v*.tar.gz asset from the VSCode JS Debug releases.
    For example, download js-debug-dap-v1.96.0.tar.gz, which is the latest version at the time of writing.

  2. Extract the archive into any folder (e.g., /home/path/to/dap). The extracted folder should contain the DAP server at /js-debug/src/dapDebugServer.js.

  3. Create a DAP Run/Debug configuration:

    DAP Configuration Type

  4. In the Server tab, click on create a new server:

    Create a new server

  5. It opens a new dialog to create DAP server, select VSCode JS Debug template: Select Template

  6. After clicking on OK button, it will select the new server and pre-fill configurations:

Select New server

This will automatically populate:

  • the server name
  • the command which starts the DAP server which should look like this:
node <<insert base directory>>/js-debug/src/dapDebugServer.js ${port} 127.0.0.1

Replace <<insert base directory>> with the directory (e.g., /home/path/to/dap) where you extracted the DAP server. For example:

node /home/path/to/dap/js-debug/src/dapDebugServer.js ${port} 127.0.0.1

The ${port} argument will be replaced with a free port when the run configuration starts.

  • the Connect to the server by waiting option is set to Log pattern before processing with:
Debug server listening at

This means the DAP (Debug Adapter Protocol) client will connect to the DAP server when this trace appears in the console:

node /home/path/to/dap/js-debug-dap-v1.96.0/js-debug/src/dapDebugServer.js 56425 127.0.0.1
Debug server listening at 127.0.0.1:56425
  1. Enable DAP server traces

If you wish to show DAP request/response traces when you will debug:

Show DAP traces

you need to select Trace with verbose.

Set verbose traces

Configure file mappings

To allows settings breakpoints to JavaScript, TypeScript, etc files, you need configure mappings in the Mappings tab. As you have selected VSCode JS Debug server, it will automatically populate the file mappings like this:

File mappings

Configure the JavaScript file to run/debug

  1. Fill in the Configuration tab:
  • the working directory (usually the project's root directory)
  • the path to the test.js file.

DAP Configuration Type/Configuration

  1. Select Launch as Debug mode.
  2. The DAP parameters of the launch should look like this:
{
"type": "pwa-node",
"name": "Launch JavaScript file",
"request": "launch",
"program": "${file}",
"cwd": "${workspaceFolder}"
}

When the run configuration starts:

  • ${workspaceFolder} will be replaced with the working directory you specified.
  • ${file} will be replaced with the full path to test.js.

Set Breakpoint

After applying the run configuration, you should set a breakpoint to files which matches file mappings. Set a breakpoint in the test.js file:

Set Breakpoint

Debugging

You can start the run configuration in either Run or Debug mode. Once started, you should see DAP traces in the console:

Debugging / Console

You will also see Threads and Variables:

Debugging / Threads

Configure the TypeScript file to run/debug

Let’s debugging the following test.ts file:

class Greeter {
greeting: string;

constructor(message: string) {
this.greeting = message;
}

greet() {
return "Hello, " + this.greeting;
}
}

let greeter = new Greeter("world");
console.log(greeter.greet())

Set Breakpoint

Compile TypeScript

Create a tsconfig.json file like this:

{
"compilerOptions": {
"target": "ES6",
"module": "CommonJS",
"outDir": "out",
"sourceMap": true
}
}

Execute tsc command to generate source maps in the out folder:

  • test.js
  • test.js.map

Configure the TypeScript file to run/debug

Select the Launch TypeScript file configuration:

Select TypeScript Launch

which will update the DAP parameters like this:

{
"type": "pwa-node",
"name": "Launch TypeScript file",
"request": "launch",
"program": "${file}",
"cwd": "${workspaceFolder}",
"outFiles": [
"${workspaceFolder}/**/*.(m|c|)js",
"!**/node_modules/**"
],
"sourceMaps": true,
"__workspaceFolder": "${workspaceFolder}"
}

Update the path with the test.ts file.

DAP Configuration Type/Configuration

Debugging

TypeScript debugging should be available:

Debugging TypeScript

How to integrate your Debug Adapter Server in an IntelliJ plugin.

Developer Guide

DAP support provides a generic DAP configuration interface that allows to configure any DAP server. The fact that it is generic can confuse the user with all the possible configurations.

To improve the user experience, it is possible to register a custom DAP server via the experimental com.redhat.devtools.lsp4ij.debugAdapterServer extension point which allows for example:

TypeScript DAP server

In this example we will explain how to register VSCode JS Debug DAP server with the com.redhat.devtools.lsp4ij.debugAdapterServer extension point.

Embed the DAP server

Here we will embed the VSCode JS Debug DAP server in the plugin my.plugin.id. You will need to download the js-debug-dap-v*.tar.gz asset from the VSCode JS Debug releases, unzip it and copy/paste the js-debug folder in your src/main/resources folder:

Embed VSCode JS Debug

Customize the DAP server startup

Configuring manually VSCode JS Debug DAP server can be boring:

  • you need to download js-debug-dap-v*.tar.gz, unzip it, and update the <<insert base directory>> to use the proper path of the DAP server.
  • the UI configuration is little complex.

DAP generic interface server

In this step we will register the DAP server with the com.redhat.devtools.lsp4ij.debugAdapterServer extension point.

DebugAdapterDescriptor

Create the TypeScriptDebugAdapterDescriptor class like this:

package my.dap.server;

import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.configurations.RunConfigurationOptions;
import com.intellij.execution.process.ProcessHandler;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.openapi.fileTypes.FileType;
import com.redhat.devtools.lsp4ij.dap.client.LaunchUtils;
import com.redhat.devtools.lsp4ij.dap.configurations.options.FileOptionConfigurable;
import com.redhat.devtools.lsp4ij.dap.configurations.options.WorkingDirectoryConfigurable;
import com.redhat.devtools.lsp4ij.dap.definitions.DebugAdapterServerDefinition;
import com.redhat.devtools.lsp4ij.dap.descriptors.DebugAdapterDescriptor;
import com.redhat.devtools.lsp4ij.dap.descriptors.ServerReadyConfig;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.nio.file.Path;
import java.util.Map;

import static com.redhat.devtools.lsp4ij.dap.descriptors.DebugAdapterDescriptorFactory.getDebugAdapterServerPath;

public class TypeScriptDebugAdapterDescriptor extends DebugAdapterDescriptor {

private static final String PLUGIN_ID = "my.plugin.id";

private final static Path dapServerPath;

static {
dapServerPath = getDebugAdapterServerPath(PLUGIN_ID, "js-debug/src/dapDebugServer.js");
}

public TypeScriptDebugAdapterDescriptor(@NotNull RunConfigurationOptions options,
@NotNull ExecutionEnvironment environment,
@Nullable DebugAdapterServerDefinition serverDefinition) {
super(options, environment, serverDefinition);
}

@Override
public ProcessHandler startServer() throws ExecutionException {
String command = "node " + dapServerPath.toString() + " ${port} 127.0.0.1";
GeneralCommandLine commandLine = createStartServerCommandLine(command);
return startServer(commandLine);
}

@Override
public @NotNull Map<String, Object> getDapParameters() {
// language=JSON
String launchJson = """
{
"type": "pwa-node",
"name": "Launch TypeScript file",
"request": "launch",
"program": "${file}",
"cwd": "${workspaceFolder}",
"outFiles": [
"${workspaceFolder}/**/*.(m|c|)js",
"!**/node_modules/**"
],
"sourceMaps": true,
"__workspaceFolder": "${workspaceFolder}"
}
""";
String file = ((FileOptionConfigurable) options).getFile();
String workspaceFolder = ((WorkingDirectoryConfigurable) options).getWorkingDirectory();
LaunchUtils.LaunchContext context = new LaunchUtils.LaunchContext(file, workspaceFolder);
return LaunchUtils.getDapParameters(launchJson, context);
}

@Override
public @NotNull ServerReadyConfig getServerReadyConfig(@NotNull DebugMode debugMode) {
return new ServerReadyConfig("Debug server listening at ");
}

@Override
public @Nullable FileType getFileType() {
return null;
}
}

DebugAdapterDescriptorFactory

Create the TypeScriptDebugAdapterDescriptorFactory class like this:

package my.dap.server;

import com.intellij.execution.runners.ExecutionEnvironment;
import com.redhat.devtools.lsp4ij.dap.configurations.DAPRunConfigurationOptions;
import com.redhat.devtools.lsp4ij.dap.descriptors.DebugAdapterDescriptor;
import com.redhat.devtools.lsp4ij.dap.descriptors.DebugAdapterDescriptorFactory;
import org.jetbrains.annotations.NotNull;

public class TypeScriptDebugAdapterDescriptorFactory extends DebugAdapterDescriptorFactory {

@Override
public DebugAdapterDescriptor createDebugAdapterDescriptor(@NotNull DAPRunConfigurationOptions options,
@NotNull ExecutionEnvironment environment) {
return new TypeScriptDebugAdapterDescriptor(options, environment, getServerDefinition());
}
}

debugAdapterServer extension point

Register the TypeScriptDebugAdapterDescriptorFactory class with the debugAdapterServer extension point like this:

<extensions defaultExtensionNs="com.redhat.devtools.lsp4ij">
<debugAdapterServer
id="typescript"
name="TypeScript"
factoryClass="my.dap.server.TypeScriptDebugAdapterDescriptorFactory" />
</extensions>

Test your debugAdapterServer

At this step, you can test your TypeScriptDebugAdapterDescriptorFactory with the generic Debug Adapter Protocol runconfiguration. You should see TypeScript in the existing server,

DAP generic interface server

Select it, select the TypeScript file that you wish to run in the Configuration tab and run the configuration. It should start your DAP server and execute the TypeScript file.

Conclusion

This article gives an overview of the new DAP support of LSP4IJ.

It allows to configure any DAP server with DAP configuration type.

The DAP template system allows to pre-fill the configuration fields for a given DAP server.

Feel free to contribute to LSP4IJ to add other templates like vscode-js-debug template

It also allows to integrate a DAP server via the experimental com.redhat.devtools.lsp4ij.debugAdapterServer extension point

Supporting dynamic code actions in VS Code Java

· 2 min read
Hope Hadfield
Intern @ Red Hat

I'm definitely not the first developer to say that quick fix suggestions make my life ten times easier. With dynamic proposals for code actions, my life gets even easier.

Many quick fixes have multiple suggestions for text replacement. In the Eclipse IDE, these are displayed in a dropdown selection menu. Take a look at the type mismatch quick fix below, for example:

Example of linked proposal in Eclipse

Very helpful, right? Now let's take a look at how VS Code handles the same quick fix:

Example of old proposal in VS Code

No dropdown, no options, just the first item from Eclipse's list inserted statically. Definitely less helpful.

Why is this? Well, code actions in Eclipse support dropdown choices, as well as placeholders, which enable dynamic text insertion/replacement. Code actions in VS Code, on the other hand, do not. The closest we can get to simulating this behaviour in VS Code is by using snippets, which similarly have choices/placeholders. Unfortunately, we couldn't support snippet strings in code actions without violating the Language Server Protocol specification... until now!

With the introduction and support of SnippetTextEdit in LSP and VS Code, dynamic code actions will now be supported in the upcoming release of vscode-java 1.35.0! If you can't wait, they're also available now in pre-release.

Check out the relevant PR in vscode-java and its supporting PR in JDT-LS.

The Point

Code actions, which were once completely static, now prompt you to alter relevant sections of text and give you multiple suggestions for insertion and replacement.

Example of dynamic proposal in VS Code

Pretty cool.

Limitations

This improvement doesn't come without a few minor setbacks.

For code actions that propose new types, the import for the selected type cannot be automatically resolved. Instead, after applying the code action and selecting the new type from the dropdown, the user has to either manually import the type, apply the subsequent quick fix suggestion to import the type, or issue the 'Organize Imports' command (Shift + Alt + O).

Additionally, any code action that involves multi-line text insertion will see additional indentation on all lines excluding the first one. You can see this happen in the above example. This is due to a bug in VS Code that automatically adds indentation to snippet strings.

Dropping support of older Java releases in vscode-java

· 2 min read
Roland Grunberg
Principal Software Developer @ Red Hat

A percentage (~4%) of users trying out our latest release of vscode-java 1.33.0 were probably disappointed to note that we dropped support for Java versions prior to 1.8. This wasn't something we did intentionally but it happened in the JDT Core library that we rely on for much of our Java functionality. While losing existing functionality is frustrating, I'll go over why this change was necessary and how it will set up the JDT Core project, the Java language server (JDT-LS), and vscode-java for success in the longer term.

There's a JEP for that

JEP-182 already defines a process for this exact thing. Although the policy would have to change to take into account the 6-month release cadence, it's clear that a move towards supporting fewer older releases is desired.

As of now, javac from the OpenJDK 22 developments tools no longer support a pre-1.8 source/target/release flag :

$ javac --help
...
...
--release <release>
Compile for the specified Java SE release.
Supported releases:
8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22
...
...

Maintenance burden

Going through the discussion ( https://github.com/eclipse-jdt/eclipse.jdt.core/discussions/820 ) in JDT Core to drop the support, it's clear there's a cost to supporting the older versions. It also affects the time it takes to implement new features that must deal with the added complexity from those older versions. Simplifying the codebase will translate into more features, implemented more quickly and an easier maintenance.

Perspective

So where does that leave affected users ? They could continue to stay on vscode-java 1.32.0, until they update their project to Java 1.8 or above. It's definitely not ideal for those depending on older Java versions but the newer features, and improvements should certainly make up for it.

Meet LSP4IJ, a new LSP Client for JetBrains-based IDEs

· 10 min read
Angelo Zerr
Principal Software Engineer @ Red Hat
Fred Bricon
Principal Software Engineer @ Red Hat

LSP4IJ is a new, free and open-source Language Server Protocol (LSP) client for JetBrains-based IDEs, compatible with both community and enterprise flavors.

It provides outstanding features such as:

The client already supports a significant amount of features from the LSP 3.17 specification, and more are on their way.

Why LSP4IJ?

The Devtools team at Red Hat has been working on free and open-source language servers for many years. We have offered LSP implementations for Java, YAML, XML, MicroProfile, Quarkus and Qute, that were integrated into Visual Studio Code via several extensions. Some of those language servers have also been integrated into Eclipse IDE.

By 2019, JetBrains already provided some Quarkus support in IntelliJ IDEA Ultimate to their paid customers. But when Red Hat wanted to bring the same awesome Quarkus tools VS Code already had, to the wider IntelliJ IDEA Community, an LSP client for IntelliJ was needed.

At that time, we evaluated Ballerina's lsp4intellij library, which provides advanced LSP support but has the following limitations:

  • Quarkus / Qute language servers have a complicated mechanism, delegating parts of the work to the IDE's Java support (JDT for Eclipse and Psi for IntelliJ), to avoid parsing Java classes twice (once by the language server, another the built-in Java support). We encountered numerous freezes.
  • Ballerina implements certain LSP features without reusing IntelliJ built-in extension points (ex: hover, go to declaration)
  • Tracing server status (starting, started, stopping, stopped) is difficult, which makes server startup issues hard to troubleshoot.
  • Ballerina features are mostly based on a Timeout system, which stops LSP requests after a defined time.

To integrate our Quarkus and Qute language servers, the LSP support in IntelliJ needed to:

  • never freeze, even when the language server cannot be started.
  • not rely on a timeout system, but instead properly manage LSP request cancellations (when the file is modified for example)
  • provide a UI allowing easy tracking of the state of the language servers
  • provide an LSP console that displays LSP traces and server logs.

For these reasons, we developed the LSP support we needed directly within the Quarkus Tools for IntelliJ plugin. It actually started as a port of the Eclipse LSP4E project. It took us several years, but we ended up with an LSP support that was mature and efficient.

In 2023, JetBrains finally decided to start providing some LSP support, but to only make it available in their commercial products, which was a not a good fit for us as we couldn't use it for Quarkus Tools. It was also lacking some features we really, really, liked in our LSP client, notably the LSP Consoles view.

Seeing our own LSP support held up pretty well for Quarkus and Qute, we decided to extract it into its own standalone project, to provide generic LSP support, for all JetBrains products, notably the community ones. Hence LSP4IJ was born.

LSP support overview

LSP features

LSP4IJ implements basic LSP features like completion, validation, hover, go to definition, but also more advanced ones, such as codelens, inlay hints, quickfixes, rename, signature help and others.

Here's a quick example showcasing the gopls language server in action:

Go Demo

The LSP Support page provides a complete overview of what LSP4IJ supports and details how it leverages specific IntelliJ extension points. While most features work out-of-the-box, a few cases require special configuration.

Cancellation Support

In order to avoid using timeouts (that might still freeze the IDE), LSP4IJ implements Cancellation Support to stop LSP requests when the file changes or when the IDE indicates the some requests (e.g. completion) are no longer relevant.

LSP consoles view

Although not often useful to users, the LSP consoles view is extremely valuable when one needs to troubleshoot issues with the language servers. All servers can be configured to log traces, as simple or verbose messages. This console view is similar to Visual Studio Code's Output view, only more convenient, as message contents are collapsible:

LSP console

Installation

LSP4IJ requires at least Java 17 and IntelliJ-based IDEs 2023.2 at the moment.

The LSP4IJ plugin is available in the stable channel of the JetBrains Plugin Repository.

Quickly get started with LSP4IJ

If you want to quickly test your language server with LSP4IJ without having to develop a plugin or if you're unfamiliar with IntelliJ plugin development, LSP4IJ offers the possibility of integrating any language server (only stdio mode is supported at the moment) with simple settings where you only need to define:

  • the language server launch command
  • the mapping between the language server and the files targeted by the language server

Example: Adding TypeScript support

Free/community IntelliJ-based IDEs only provide basic support for TypeScript in the form of syntax highlighting. In this section, we'll explain step-by-step how to integrate the TypeScript language server into your IntelliJ IDE, without having to develop anything, and immediately benefit from TypeScript, React and JavaScript support (completion, validation, quickfixes, codelens, etc):

TypeScriptServerDemo

Step 1: Install the language server

This step describes how to install the language server. In our case, typescript-language-server is Node.js application and thus requires the Node.js runtime to run.

You will need to:

npm install -g typescript-language-server typescript

It will install:

  • TypeScript language server: this project delegates the LSP operations (completion, diagnostics, etc) to the tsserver from TypeScript which doesn't support LSP itself.
  • TypeScript which includes tsserver.
  • As the command will add typescript-language-server to your OS PATH, you will probably have to close and reopen your IDE for the command to be accessible by LSP4IJ.

Step 2: Create a language server in LSP4IJ

In order to create a new User-defined language server, you need to open the New Language Server dialog, either:

  • from the menu on the right of the LSP console:

New Language Server From Console

  • or with the [+] on the top of the language server settings:

New Language Server From Settings

Once you clicked on either of them, the dialog will appear:

New Language Server Dialog

To quickly configure TypeScript support, you could use the TypeScript template which will pre-fill all fields (command and mappings), but in order to better understand the process, let's do that manually:

Define launch command

In the Server tab, set:

  • Name as TypeScript Language Server.
  • Command as typescript-language-server --stdio

New Language Server Dialog with TypeScript

Note that you can use macros in your commands, to make them more portable, should you choose to export you server.

Define mappings

In the Mappings > File name patterns tab, associate *.ts, *.tsx, *.jsx files to the language server as shown below :

File Name PatternLanguage Id
*.jsxjavascriptreact
*.tstypescript
*.tsxtypescriptreact

File name patterns

The value in the Language Id column must be one of the language identifiers defined in the LSP specification.

Define configuration

By default, codelens and inlayhint are not available in the TypeScript Language Server, you can enable them by filling the Configuration tab with the following JSON settings:

{
"typescript": {
"inlayHints": {
"includeInlayEnumMemberValueHints": true,
"includeInlayFunctionLikeReturnTypeHints": true,
"includeInlayFunctionParameterTypeHints": true,
"includeInlayParameterNameHints": "all",
"includeInlayParameterNameHintsWhenArgumentMatchesName": true,
"includeInlayPropertyDeclarationTypeHints": true,
"includeInlayVariableTypeHints": true,
"includeInlayVariableTypeHintsWhenTypeMatchesName": true
},
"implementationsCodeLens": {
"enabled": true
},
"referencesCodeLens": {
"enabled": true,
"showOnAllFunctions": true
}
}
}

TypeScript Language Server configuration page

Click the OK button, and you should see the TypeScript Language Server in the Language Servers view:

TypeScript Language Server in the console view

Create a ts file and open it. The TypeScript Language Server should start and you should now be able to use TypeScript support with completion, hover, linting...

Using a Template

LSP4IJ provides a few templates for various language servers. You can select the TypeScript Language Server template which will pre-fill all fields previously described (server name, command and mappings).

Export server

If you want to share your language server settings, you can export it to generate a zip file, that, once shared and unzipped to a directory, can be imported.

How to integrate your language server in an IntelliJ plugin.

Manually defining a language server via settings allows you to add a language server into IntelliJ in a few minutes, but if you need to provide better integration with the IDE, embedding the language server definition in an IntelliJ plugin will be a better solution:

  • it is possible to embed the language server and/or provide a mechanism to download/update the language server, etc. make it easier for users to get started.
  • advanced language servers require implementing specific client-side commands, which is only possible through the development of an IntelliJ plugin.

The Developer Guide provides step-by-step instructions for contributing an LSP language server in your IntelliJ plugin.

The following example shows how to integrate the TypeScript Language Server in an IntelliJ plugin. In your plugin.xml, you would declare the server like this:

<extensions defaultExtensionNs="com.redhat.devtools.lsp4ij">
<server id="typeScriptLanguageServerId"
name="TypeScript Language Server"
factoryClass="com.yourplugin.lsp.TypeScriptLanguageServerFactory">
<description><![CDATA[
Some description written in HTML, that will be displayed in the LSP consoles view and Language Servers settings.
]]>
</description>
</server>
</extensions>

and mappings like this by using File name pattern mapping:

<extensions defaultExtensionNs="com.redhat.devtools.lsp4ij">

<fileNamePatternMapping patterns="*.ts"
serverId="typeScriptLanguageServerId"
languageId="typescript"/>

</extensions>

The com.yourplugin.lsp.TypeScriptLanguageServerFactory factory class looks like this:

package com.yourplugin.lsp;

import com.intellij.openapi.project.Project;
import com.redhat.devtools.lsp4ij.LanguageServerFactory;
import com.redhat.devtools.lsp4ij.client.LanguageClientImpl;
import com.redhat.devtools.lsp4ij.server.StreamConnectionProvider;
import org.jetbrains.annotations.NotNull;

public class TypeScriptLanguageServerFactory implements LanguageServerFactory {

@Override
public @NotNull StreamConnectionProvider createConnectionProvider(@NotNull Project project) {
return new TypeScriptLanguageServer(project);
}

}
package com.yourplugin.lsp;

import com.redhat.devtools.lsp4ij.server.ProcessStreamConnectionProvider;

import java.util.List;

public class TypeScriptLanguageServer extends ProcessStreamConnectionProvider {

public MyLanguageServer() {
List<String> commands = List.of("typescript-language-server", "--stdio");
super.setCommands(commands);
}
}

This code assumes typescript-language-server is available on the PATH, but it could be improved to target an embedded TypeScript language server inside your IntelliJ plugin.

Conclusion

This article only scratches the surface of what LSP4IJ provides. You can find more documentation in:

Creating a new LSP client for JetBrains-based IDEs has been a very humbling and rewarding experience. With each new language server we tested, we found slightly different behaviors to account for, eventually making LSP4IJ more and more robust. The initial feedback from the community has been very encouraging so far. We hope you will find LSP4IJ useful!

If you find any bugs or can think of ideas for some great new features, please don’t hesitate to head over to our Github repository and open a ticket. Since our test framework is built around LSP requests, you just need to copy traces from the LSP Consoles view so we can reproduce issues you find with your language server and fix them.

Create a Test Kubernetes Cluster With an Unprivileged User

· 5 min read
David Thompson
Software Developer @ Red Hat

Motivation

You are working on a software tool that interacts with Kubernetes clusters. You would like to troubleshoot bugs arising due to insufficient permissions by simulating an environment where the user has heavily restricted permissions on the cluster.

Prerequisites

  • This tutorial assumes you are using Linux or macOS. The same general steps should work on Windows, except the paths and commands might be slightly different.
  • Install kind, needed to create the cluster.
  • (Optional) install kubectx, helpful for switching Kubernetes contexts.
note

If you don't install kubectx, you can change Kubernetes contexts by opening ~/.kube/config, and modifying the value of current-context to the name of the context you want to work with. However, I find that it's much faster to use kubectx. The tutorial is written assuming you have installed kubectx.

Steps

Creating the Cluster

First, create the kind cluster:

kind create cluster

This should take around a minute.

Creating the Service Account

Once the cluster is up and running, create a service account:

kubectl create serviceaccount my-service-account

And create a token associated with the service account:

kubectl create token my-service-account --duration 72h

This second command will output the token into the console. Make sure to copy this token into your clipboard.

Open up your kube config (~/.kube/config), and create a new user entry in the users section:

 users:
+- name: my-service-account
+ user:
+ token: # PASTE TOKEN HERE AND REMOVE NEWLINES
- name: kind-kind
user:
warning

Make sure to add the token into the user entry and remove any newlines that are placed in the middle of the token, otherwise you will be unable to use the service account

Then, go up to the contexts section, and add the following new context:

 contexts:
- context:
cluster: kind-kind
namespace: default
user: kind-kind
name: kind-kind
+- context:
+ cluster: kind-kind
+ namespace: default
+ user: my-service-account
+ name: my-service-account-ctx
current-context: kind-kind
kind: Config

Creating the Role and Role Binding

Next, create the following file, service-account-permissions.yaml:

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: can-list-pod
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: can-list-pod-binding
subjects:
- kind: User
name: system:serviceaccount:default:my-service-account
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: can-list-pod
apiGroup: rbac.authorization.k8s.io

Then, apply this YAML to the cluster using the following command:

kubectl create -f service-account-permissions.yaml

This creates a ClusterRole, which is a set of actions that an account is allowed to perform across all namespaces in a cluster. The ClusterRole is called "can-list-pod", and it allows the user to list pods in all namespaces. The YAML file also creates a ClusterRoleBinding, which specifies that "my-service-account", the service account we just created, should have the ClusterRole "can-list-pod".

Next, we need to switch to the newly created service account. To do this, run kubectx my-service-account-ctx to switch to the context that we created by editing ~/.kube/config.

Testing the account

In order to test that the account was created successfully, try listing the namespaces on the cluster:

kubectl get namespace

You should get an "unauthorized" warning, because the only thing the service account is allowed to do on the cluster is list pods.

Next, let's verify that you can list pods. We should create a pod on the cluster so that there is something to list. Use kubectx kind-kind to switch back to the kind-kind context, so that you have permissions to create pods.

Then, run:

kubectl create deployment --image quay.io/davthomp/pet-alpine my-deployment

This creates a deployment, which will automatically create and manage a pod.

About the container image

The container image that's being run is from my Quay repository. It runs a "Hello World" webapp written in Go, i.e. it serves the text "hello" over HTTP on port 8080 at the endpoint /hello. It uses Alpine Linux as the base image.

Then, switch back to the my-service-account-ctx context using kubectx my-service-account-ctx, and then try to list the pods:

kubectl get pods

You should see one pod that's listed: the pod managed by the deployment we created.

Next Steps

You've successfully set up a service account with heavily restricted access to the cluster.

If you want to give the service account more permissions, you can modify and reapply service-account-permissions.yaml. You will need to switch back to the kind-kind user in order to reapply the changes. Refer to the Kubernetes documentation on role-based access control for more information on setting up the permissions for the account.

Cleaning Up

Once you are done with the cluster, make sure to delete it. Also, make sure to remove the new user and context that you created from your ~/.kube/config, since they will no longer exist when you delete the cluster.

Check out our developer tools

If you liked this blog post, make sure to check out VS Code OpenShift Toolkit or Intellij OpenShift Toolkit, which enable you to interact with your Kubernetes or OpenShift cluster right from your IDE.